diff options
Diffstat (limited to 'src/qml')
145 files changed, 18190 insertions, 5229 deletions
diff --git a/src/qml/compiler/qqmlirbuilder_p.h b/src/qml/compiler/qqmlirbuilder_p.h index 22bc2d2953..298fe7dd92 100644 --- a/src/qml/compiler/qqmlirbuilder_p.h +++ b/src/qml/compiler/qqmlirbuilder_p.h @@ -60,10 +60,6 @@ #include <QTextStream> #include <QCoreApplication> -#ifndef V4_BOOTSTRAP -#include <private/qqmlpropertycache_p.h> -#endif - QT_BEGIN_NAMESPACE class QQmlPropertyCache; diff --git a/src/qml/compiler/qqmlpropertycachecreator_p.h b/src/qml/compiler/qqmlpropertycachecreator_p.h index 6bee599c0a..7d416561bb 100644 --- a/src/qml/compiler/qqmlpropertycachecreator_p.h +++ b/src/qml/compiler/qqmlpropertycachecreator_p.h @@ -52,6 +52,7 @@ #include <private/qqmlvaluetype_p.h> #include <private/qqmlengine_p.h> +#include <private/qqmlmetaobject_p.h> QT_BEGIN_NAMESPACE diff --git a/src/qml/compiler/qqmlpropertyvalidator.cpp b/src/qml/compiler/qqmlpropertyvalidator.cpp index 1beaac8095..4714f505a7 100644 --- a/src/qml/compiler/qqmlpropertyvalidator.cpp +++ b/src/qml/compiler/qqmlpropertyvalidator.cpp @@ -558,6 +558,7 @@ QQmlCompileError QQmlPropertyValidator::validateLiteralBinding(QQmlPropertyCache } break; case QVariant::RegExp: + case QVariant::RegularExpression: return warnOrError(tr("Invalid property assignment: regular expression expected; use /pattern/ syntax")); default: { // generate single literal value assignment to a list property if required 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 <private/qqmlcustomparser_p.h> #include <private/qqmlvmemetaobject_p.h> #include <private/qqmlcomponent_p.h> -#include <private/qqmldelegatecomponent_p.h> #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)) { diff --git a/src/qml/compiler/qv4bytecodehandler.cpp b/src/qml/compiler/qv4bytecodehandler.cpp index 92b112c2fa..f9f755b8c0 100644 --- a/src/qml/compiler/qv4bytecodehandler.cpp +++ b/src/qml/compiler/qv4bytecodehandler.cpp @@ -58,9 +58,10 @@ ByteCodeHandler::~ByteCodeHandler() Q_UNUSED(base_ptr); \ _currentOffset = _nextOffset; \ _nextOffset = code - start; \ - startInstruction(Instr::Type::instr); \ - INSTR_##instr(DISPATCH) \ - endInstruction(Instr::Type::instr); \ + if (startInstruction(Instr::Type::instr) == ProcessInstruction) { \ + INSTR_##instr(DISPATCH) \ + endInstruction(Instr::Type::instr); \ + } \ continue; \ } diff --git a/src/qml/compiler/qv4bytecodehandler_p.h b/src/qml/compiler/qv4bytecodehandler_p.h index 797d25b8d0..f1e7c99447 100644 --- a/src/qml/compiler/qv4bytecodehandler_p.h +++ b/src/qml/compiler/qv4bytecodehandler_p.h @@ -105,7 +105,8 @@ public: protected: FOR_EACH_MOTH_INSTR(BYTECODE_HANDLER_DEFINE_VIRTUAL_BYTECODE_HANDLER) - virtual void startInstruction(Moth::Instr::Type instr) = 0; + enum Verdict { ProcessInstruction, SkipInstruction }; + virtual Verdict startInstruction(Moth::Instr::Type instr) = 0; virtual void endInstruction(Moth::Instr::Type instr) = 0; private: diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp index e0d259bd0c..88d3dbe9c5 100644 --- a/src/qml/compiler/qv4codegen.cpp +++ b/src/qml/compiler/qv4codegen.cpp @@ -274,7 +274,7 @@ Codegen::Reference Codegen::unop(UnaryOperation op, const Reference &expr) case Not: return Reference::fromConst(this, Encode(!v.toBoolean())); case UMinus: - return Reference::fromConst(this, Runtime::method_uMinus(v)); + return Reference::fromConst(this, Runtime::UMinus::call(v)); case UPlus: return expr; case Compl: @@ -294,8 +294,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: { @@ -314,8 +314,8 @@ Codegen::Reference Codegen::unop(UnaryOperation op, const Reference &expr) if (!exprAccept(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); @@ -340,8 +340,8 @@ Codegen::Reference Codegen::unop(UnaryOperation op, const Reference &expr) if (!exprAccept(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); @@ -1190,6 +1190,7 @@ bool Codegen::visit(ArrayPattern *ast) ControlFlowLoop flow(this, &end, &in, cleanup); in.link(); + bytecodeGenerator->addLoopStart(in); iterator.loadInAccumulator(); Instruction::IteratorNext next; next.value = lhsValue.stackSlot(); @@ -3189,22 +3190,26 @@ bool Codegen::visit(DoWhileStatement *ast) BytecodeGenerator::Label end = bytecodeGenerator->newLabel(); ControlFlowLoop flow(this, &end, &cond); - bytecodeGenerator->jump().link(body); - cond.link(); - bytecodeGenerator->addLoopStart(cond); - - if (!AST::cast<TrueLiteral *>(ast->expression)) { - TailCallBlocker blockTailCalls(this); - condition(ast->expression, &body, &end, true); - } + // special case that is not a loop: + // do {...} while (false) + if (!AST::cast<FalseLiteral *>(ast->expression)) + bytecodeGenerator->addLoopStart(body); body.link(); statement(ast->statement); setJumpOutLocation(bytecodeGenerator, ast->statement, ast->semicolonToken); - if (!AST::cast<FalseLiteral *>(ast->expression)) - bytecodeGenerator->jump().link(cond); + cond.link(); + if (AST::cast<TrueLiteral *>(ast->expression)) { + // do {} while (true) -> just jump back to the loop body, no need to generate a condition + bytecodeGenerator->jump().link(body); + } else if (AST::cast<FalseLiteral *>(ast->expression)) { + // do {} while (false) -> fall through, no need to generate a condition + } else { + TailCallBlocker blockTailCalls(this); + condition(ast->expression, &body, &end, false); + } end.link(); diff --git a/src/qml/compiler/qv4compileddata.cpp b/src/qml/compiler/qv4compileddata.cpp index dc5466371d..f280994d54 100644 --- a/src/qml/compiler/qv4compileddata.cpp +++ b/src/qml/compiler/qv4compileddata.cpp @@ -46,7 +46,6 @@ #include <private/qv4lookup_p.h> #include <private/qv4regexpobject_p.h> #include <private/qv4regexp_p.h> -#include <private/qqmlpropertycache_p.h> #include <private/qqmltypeloader_p.h> #include <private/qqmlengine_p.h> #include <private/qv4vme_moth_p.h> diff --git a/src/qml/compiler/qv4compileddata_p.h b/src/qml/compiler/qv4compileddata_p.h index 23e33247a8..4cfd2d86e8 100644 --- a/src/qml/compiler/qv4compileddata_p.h +++ b/src/qml/compiler/qv4compileddata_p.h @@ -66,17 +66,22 @@ #include <private/qqmljsastfwd_p.h> #ifndef V4_BOOTSTRAP #include <private/qqmltypenamecache_p.h> -#include <private/qqmlpropertycache_p.h> +#include <private/qqmlpropertycachevector_p.h> #include "private/qintrusivelist_p.h" #endif QT_BEGIN_NAMESPACE // Bump this whenever the compiler data structures change in an incompatible way. -#define QV4_DATA_STRUCTURE_VERSION 0x21 +// +// 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 0x22 // Add trace slot to UPlus class QIODevice; -class QQmlPropertyCache; class QQmlPropertyData; class QQmlTypeNameCache; class QQmlScriptData; diff --git a/src/qml/compiler/qv4compilercontext.cpp b/src/qml/compiler/qv4compilercontext.cpp index b9ab4e5173..52215c2ce6 100644 --- a/src/qml/compiler/qv4compilercontext.cpp +++ b/src/qml/compiler/qv4compilercontext.cpp @@ -426,9 +426,7 @@ bool Context::canUseTracingJit() const if (!onlyTrace.isEmpty()) return onlyTrace.contains(name); - //### the next condition should be refined and have the IR distinguish between escaping and - // non-escaping locals - return !hasTry && !requiresExecutionContext && !hasNestedFunctions; + return true; #else return false; #endif diff --git a/src/qml/compiler/qv4instr_moth.cpp b/src/qml/compiler/qv4instr_moth.cpp index 345f03ae8a..b019f191fa 100644 --- a/src/qml/compiler/qv4instr_moth.cpp +++ b/src/qml/compiler/qv4instr_moth.cpp @@ -593,6 +593,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 26901c297c..6a8c9a9549 100644 --- a/src/qml/compiler/qv4instr_moth_p.h +++ b/src/qml/compiler/qv4instr_moth_p.h @@ -167,7 +167,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/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 new file mode 100644 index 0000000000..d3f6176c70 --- /dev/null +++ b/src/qml/doc/snippets/qml/tablemodel/fruit-example-delegatechooser.qml @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** 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.Controls 2.5 +import Qt.labs.qmlmodels 1.0 + +ApplicationWindow { + width: 400 + height: 400 + visible: true + + TableView { + anchors.fill: parent + columnSpacing: 1 + rowSpacing: 1 + 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 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] + } +//![delegate] + delegate: DelegateChooser { + DelegateChoice { + column: 0 + delegate: CheckBox { + checked: model.display + onToggled: model.display = checked + } + } + DelegateChoice { + column: 1 + delegate: SpinBox { + value: model.display + onValueModified: model.display = value + } + } + DelegateChoice { + delegate: TextField { + text: model.display + selectByMouse: true + implicitWidth: 140 + onAccepted: model.display = text + } + } + } +//![delegate] + } +} +//![file] diff --git a/src/qml/doc/snippets/qml/tablemodel/fruit-example-simpledelegate.qml b/src/qml/doc/snippets/qml/tablemodel/fruit-example-simpledelegate.qml new file mode 100644 index 0000000000..f51c1818c3 --- /dev/null +++ b/src/qml/doc/snippets/qml/tablemodel/fruit-example-simpledelegate.qml @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** 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: "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 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] + } +//![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/jit/jit.pri b/src/qml/jit/jit.pri index 2c664af188..bc0d20dba7 100644 --- a/src/qml/jit/jit.pri +++ b/src/qml/jit/jit.pri @@ -2,13 +2,42 @@ INCLUDEPATH += $$PWD INCLUDEPATH += $$OUT_PWD SOURCES += \ - $$PWD/qv4jithelpers.cpp \ $$PWD/qv4baselinejit.cpp \ $$PWD/qv4baselineassembler.cpp \ $$PWD/qv4assemblercommon.cpp HEADERS += \ - $$PWD/qv4jithelpers_p.h \ $$PWD/qv4baselinejit_p.h \ $$PWD/qv4baselineassembler_p.h \ $$PWD/qv4assemblercommon_p.h + +qtConfig(qml-tracing) { +SOURCES += \ + $$PWD/qv4ir.cpp \ + $$PWD/qv4operation.cpp \ + $$PWD/qv4node.cpp \ + $$PWD/qv4graph.cpp \ + $$PWD/qv4graphbuilder.cpp \ + $$PWD/qv4lowering.cpp \ + $$PWD/qv4tracingjit.cpp \ + $$PWD/qv4mi.cpp \ + $$PWD/qv4domtree.cpp \ + $$PWD/qv4schedulers.cpp \ + $$PWD/qv4blockscheduler.cpp \ + $$PWD/qv4loopinfo.cpp + +HEADERS += \ + $$PWD/qv4ir_p.h \ + $$PWD/qv4operation_p.h \ + $$PWD/qv4runtimesupport_p.h \ + $$PWD/qv4node_p.h \ + $$PWD/qv4graph_p.h \ + $$PWD/qv4graphbuilder_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/qv4baselineassembler.cpp b/src/qml/jit/qv4baselineassembler.cpp index 25c74e74e8..238c11f478 100644 --- a/src/qml/jit/qv4baselineassembler.cpp +++ b/src/qml/jit/qv4baselineassembler.cpp @@ -943,7 +943,7 @@ void BaselineAssembler::uminus() saveAccumulatorInFrame(); pasm()->prepareCallWithArgCount(1); pasm()->passAccumulatorAsArg(0); - ASM_GENERATE_RUNTIME_CALL(Runtime::method_uMinus, CallResultDestination::InAccumulator); + ASM_GENERATE_RUNTIME_CALL(UMinus, CallResultDestination::InAccumulator); checkException(); } @@ -1044,7 +1044,7 @@ void BaselineAssembler::add(int lhs) pasm()->passAccumulatorAsArg(2); pasm()->passJSSlotAsArg(lhs, 1); pasm()->passEngineAsArg(0); - ASM_GENERATE_RUNTIME_CALL(Runtime::method_add, CallResultDestination::InAccumulator); + ASM_GENERATE_RUNTIME_CALL(Add, CallResultDestination::InAccumulator); checkException(); // done. @@ -1196,7 +1196,7 @@ void BaselineAssembler::mul(int lhs) pasm()->prepareCallWithArgCount(2); pasm()->passAccumulatorAsArg(1); pasm()->passJSSlotAsArg(lhs, 0); - ASM_GENERATE_RUNTIME_CALL(Runtime::method_mul, CallResultDestination::InAccumulator); + ASM_GENERATE_RUNTIME_CALL(Mul, CallResultDestination::InAccumulator); checkException(); // done. @@ -1209,7 +1209,7 @@ void BaselineAssembler::div(int lhs) pasm()->prepareCallWithArgCount(2); pasm()->passAccumulatorAsArg(1); pasm()->passJSSlotAsArg(lhs, 0); - ASM_GENERATE_RUNTIME_CALL(Runtime::method_div, CallResultDestination::InAccumulator); + ASM_GENERATE_RUNTIME_CALL(Div, CallResultDestination::InAccumulator); checkException(); } @@ -1219,7 +1219,7 @@ void BaselineAssembler::mod(int lhs) pasm()->prepareCallWithArgCount(2); pasm()->passAccumulatorAsArg(1); pasm()->passJSSlotAsArg(lhs, 0); - ASM_GENERATE_RUNTIME_CALL(Runtime::method_mod, CallResultDestination::InAccumulator); + ASM_GENERATE_RUNTIME_CALL(Mod, CallResultDestination::InAccumulator); checkException(); } @@ -1239,7 +1239,7 @@ void BaselineAssembler::sub(int lhs) pasm()->prepareCallWithArgCount(2); pasm()->passAccumulatorAsArg(1); pasm()->passJSSlotAsArg(lhs, 0); - ASM_GENERATE_RUNTIME_CALL(Runtime::method_sub, CallResultDestination::InAccumulator); + ASM_GENERATE_RUNTIME_CALL(Sub, CallResultDestination::InAccumulator); checkException(); // done. @@ -1269,7 +1269,7 @@ void BaselineAssembler::cmpeqInt(int lhs) else pasm()->move(PlatformAssembler::StackPointerRegister, pasm()->registerForArg(1)); pasm()->pushAccumulatorAsArg(0); - pasm()->callRuntimeUnchecked("Runtime::method_equal", (void*)Runtime::method_equal); + pasm()->callRuntimeUnchecked("Equal", (void*)Runtime::Equal::call); pasm()->saveReturnValueInAccumulator(); if (PlatformAssembler::ArgInRegCount < 2) pasm()->addPtr(TrustedImm32(2 * PlatformAssembler::PointerSize), PlatformAssembler::StackPointerRegister); @@ -1293,7 +1293,7 @@ void BaselineAssembler::cmpneInt(int lhs) else pasm()->move(PlatformAssembler::StackPointerRegister, pasm()->registerForArg(1)); pasm()->pushAccumulatorAsArg(0); - pasm()->callRuntimeUnchecked("Runtime::method_notEqual", (void*)Runtime::method_notEqual); + pasm()->callRuntimeUnchecked("NotEqual", (void*)Runtime::NotEqual::call); pasm()->saveReturnValueInAccumulator(); if (PlatformAssembler::ArgInRegCount < 2) pasm()->addPtr(TrustedImm32(2 * PlatformAssembler::PointerSize), PlatformAssembler::StackPointerRegister); @@ -1314,7 +1314,6 @@ void BaselineAssembler::cmp(int cond, CmpFunc function, const char *functionName pasm()->compare32(c, PlatformAssembler::ScratchRegister, PlatformAssembler::AccumulatorRegisterValue, PlatformAssembler::AccumulatorRegisterValue); - pasm()->setAccumulatorTag(QV4::Value::ValueTypeInternal::Boolean); return PlatformAssembler::Jump(); }); @@ -1326,60 +1325,58 @@ void BaselineAssembler::cmp(int cond, CmpFunc function, const char *functionName callRuntime(functionName, reinterpret_cast<void*>(function), CallResultDestination::InAccumulator); checkException(); - pasm()->setAccumulatorTag(QV4::Value::ValueTypeInternal::Boolean); // done. done.link(pasm()); + pasm()->setAccumulatorTag(QV4::Value::ValueTypeInternal::Boolean); } void BaselineAssembler::cmpeq(int lhs) { - cmp(PlatformAssembler::Equal, &Runtime::method_compareEqual, - "Runtime::method_compareEqual", lhs); + cmp(PlatformAssembler::Equal, &Runtime::CompareEqual::call, + "CompareEqual", lhs); } void BaselineAssembler::cmpne(int lhs) { - cmp(PlatformAssembler::NotEqual, &Runtime::method_compareNotEqual, - "Runtime::method_compareNotEqual", lhs); + cmp(PlatformAssembler::NotEqual, &Runtime::CompareNotEqual::call, + "CompareNotEqual", lhs); } void BaselineAssembler::cmpgt(int lhs) { - cmp(PlatformAssembler::GreaterThan, &Runtime::method_compareGreaterThan, - "Runtime::method_compareGreaterThan", lhs); + cmp(PlatformAssembler::GreaterThan, &Runtime::CompareGreaterThan::call, + "CompareGreaterThan", lhs); } void BaselineAssembler::cmpge(int lhs) { - cmp(PlatformAssembler::GreaterThanOrEqual, &Runtime::method_compareGreaterEqual, - "Runtime::method_compareGreaterEqual", lhs); + cmp(PlatformAssembler::GreaterThanOrEqual, &Runtime::CompareGreaterEqual::call, + "CompareGreaterEqual", lhs); } void BaselineAssembler::cmplt(int lhs) { - cmp(PlatformAssembler::LessThan, &Runtime::method_compareLessThan, - "Runtime::method_compareLessThan", lhs); + cmp(PlatformAssembler::LessThan, &Runtime::CompareLessThan::call, + "CompareLessThan", lhs); } void BaselineAssembler::cmple(int lhs) { - cmp(PlatformAssembler::LessThanOrEqual, &Runtime::method_compareLessEqual, - "Runtime::method_compareLessEqual", lhs); + cmp(PlatformAssembler::LessThanOrEqual, &Runtime::CompareLessEqual::call, + "CompareLessEqual", lhs); } void BaselineAssembler::cmpStrictEqual(int lhs) { - cmp(PlatformAssembler::Equal, &RuntimeHelpers::strictEqual, + cmp(PlatformAssembler::Equal, &Runtime::CompareStrictEqual::call, "RuntimeHelpers::strictEqual", lhs); } void BaselineAssembler::cmpStrictNotEqual(int lhs) { - cmp(PlatformAssembler::Equal, &RuntimeHelpers::strictEqual, - "RuntimeHelpers::strictEqual", lhs); - pasm()->xor32(TrustedImm32(1), PlatformAssembler::AccumulatorRegisterValue); - pasm()->setAccumulatorTag(QV4::Value::ValueTypeInternal::Boolean); + cmp(PlatformAssembler::NotEqual, &Runtime::CompareStrictNotEqual::call, + "RuntimeHelpers::strictNotEqual", lhs); } int BaselineAssembler::jump(int offset) @@ -1481,7 +1478,7 @@ void BaselineAssembler::saveAccumulatorInFrame() static ReturnedValue TheJitIs__Tail_Calling__ToTheRuntimeSoTheJitFrameIsMissing(CppStackFrame *frame, ExecutionEngine *engine) { - return Runtime::method_tailCall(frame, engine); + return Runtime::TailCall::call(frame, engine); } void BaselineAssembler::jsTailCall(int func, int thisObject, int argc, int argv) @@ -1588,9 +1585,8 @@ void BaselineAssembler::pushCatchContext(int index, int name) pasm()->prepareCallWithArgCount(3); pasm()->passInt32AsArg(name, 2); pasm()->passInt32AsArg(index, 1); - pasm()->passJSSlotAsArg(CallData::Context, 0); - ASM_GENERATE_RUNTIME_CALL(Runtime::method_createCatchContext, CallResultDestination::InAccumulator); - pasm()->storeAccumulator(pasm()->contextAddress()); + pasm()->passEngineAsArg(0); + ASM_GENERATE_RUNTIME_CALL(PushCatchContext, CallResultDestination::Ignore); } void BaselineAssembler::popContext() @@ -1610,7 +1606,7 @@ void BaselineAssembler::deadTemporalZoneCheck(int offsetForSavedIP, int variable prepareCallWithArgCount(2); passInt32AsArg(variableName, 1); passEngineAsArg(0); - ASM_GENERATE_RUNTIME_CALL(Runtime::method_throwReferenceError, CallResultDestination::Ignore); + ASM_GENERATE_RUNTIME_CALL(ThrowReferenceError, CallResultDestination::Ignore); gotoCatchException(); valueIsAliveJump.link(pasm()); } diff --git a/src/qml/jit/qv4baselineassembler_p.h b/src/qml/jit/qv4baselineassembler_p.h index c39d002bf9..3bbaefd000 100644 --- a/src/qml/jit/qv4baselineassembler_p.h +++ b/src/qml/jit/qv4baselineassembler_p.h @@ -65,7 +65,7 @@ namespace JIT { #define GENERATE_RUNTIME_CALL(function, destination) \ callRuntime(JIT_STRINGIFY(function), \ - reinterpret_cast<void *>(&function), \ + reinterpret_cast<void *>(&Runtime::function::call), \ destination) #define GENERATE_TAIL_CALL(function) \ tailCallRuntime(JIT_STRINGIFY(function), \ diff --git a/src/qml/jit/qv4baselinejit.cpp b/src/qml/jit/qv4baselinejit.cpp index e518fc5a0e..80155d7b20 100644 --- a/src/qml/jit/qv4baselinejit.cpp +++ b/src/qml/jit/qv4baselinejit.cpp @@ -38,7 +38,6 @@ ****************************************************************************/ #include "qv4baselinejit_p.h" -#include "qv4jithelpers_p.h" #include "qv4baselineassembler_p.h" #include <private/qv4lookup_p.h> #include <private/qv4generatorobject_p.h> @@ -77,10 +76,11 @@ void BaselineJIT::generate() #define STORE_IP() as->storeInstructionPointer(nextInstructionOffset()) #define STORE_ACC() as->saveAccumulatorInFrame() -#define BASELINEJIT_GENERATE_RUNTIME_CALL(function, destination) \ - as->GENERATE_RUNTIME_CALL(function, destination) -#define BASELINEJIT_GENERATE_TAIL_CALL(function) \ - as->GENERATE_TAIL_CALL(function) +#define BASELINEJIT_GENERATE_RUNTIME_CALL(function, destination) { \ + as->GENERATE_RUNTIME_CALL(function, destination); \ + if (Runtime::function::throws) \ + as->checkException(); \ + else {} } // this else prevents else statements after the macro from attaching to the if above void BaselineJIT::generate_Ret() { @@ -183,7 +183,7 @@ void BaselineJIT::generate_MoveRegExp(int regExpId, int destReg) as->prepareCallWithArgCount(2); as->passInt32AsArg(regExpId, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_regexpLiteral, CallResultDestination::InAccumulator); + BASELINEJIT_GENERATE_RUNTIME_CALL(RegexpLiteral, CallResultDestination::InAccumulator); as->storeReg(destReg); } @@ -192,7 +192,7 @@ void BaselineJIT::generate_LoadClosure(int value) as->prepareCallWithArgCount(2); as->passInt32AsArg(value, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_closure, CallResultDestination::InAccumulator); + BASELINEJIT_GENERATE_RUNTIME_CALL(Closure, CallResultDestination::InAccumulator); } void BaselineJIT::generate_LoadName(int name, int /*traceSlot*/) @@ -201,28 +201,24 @@ void BaselineJIT::generate_LoadName(int name, int /*traceSlot*/) as->prepareCallWithArgCount(2); as->passInt32AsArg(name, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_loadName, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(LoadName, CallResultDestination::InAccumulator); } void BaselineJIT::generate_LoadGlobalLookup(int index, int /*traceSlot*/) { as->prepareCallWithArgCount(3); as->passInt32AsArg(index, 2); - as->passEngineAsArg(1); - as->passFunctionAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Helpers::loadGlobalLookup, CallResultDestination::InAccumulator); - as->checkException(); + as->passFunctionAsArg(1); + as->passEngineAsArg(0); + BASELINEJIT_GENERATE_RUNTIME_CALL(LoadGlobalLookup, CallResultDestination::InAccumulator); } void BaselineJIT::generate_LoadQmlContextPropertyLookup(int index, int /*traceSlot*/) { - as->prepareCallWithArgCount(3); - as->passInt32AsArg(index, 2); - as->passEngineAsArg(1); - as->passFunctionAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Helpers::loadQmlContextPropertyLookup, CallResultDestination::InAccumulator); - as->checkException(); + as->prepareCallWithArgCount(2); + as->passInt32AsArg(index, 1); + as->passEngineAsArg(0); + BASELINEJIT_GENERATE_RUNTIME_CALL(LoadQmlContextPropertyLookup, CallResultDestination::InAccumulator); } void BaselineJIT::generate_StoreNameSloppy(int name) @@ -233,8 +229,7 @@ void BaselineJIT::generate_StoreNameSloppy(int name) as->passAccumulatorAsArg(2); as->passInt32AsArg(name, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_storeNameSloppy, CallResultDestination::Ignore); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(StoreNameSloppy, CallResultDestination::Ignore); } void BaselineJIT::generate_StoreNameStrict(int name) @@ -245,8 +240,7 @@ void BaselineJIT::generate_StoreNameStrict(int name) as->passAccumulatorAsArg(2); as->passInt32AsArg(name, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_storeNameStrict, CallResultDestination::Ignore); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(StoreNameStrict, CallResultDestination::Ignore); } void BaselineJIT::generate_LoadElement(int base, int /*traceSlot*/) @@ -257,8 +251,7 @@ void BaselineJIT::generate_LoadElement(int base, int /*traceSlot*/) as->passAccumulatorAsArg(2); as->passJSSlotAsArg(base, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_loadElement, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(LoadElement, CallResultDestination::InAccumulator); } void BaselineJIT::generate_StoreElement(int base, int index, int /*traceSlot*/) @@ -270,8 +263,7 @@ void BaselineJIT::generate_StoreElement(int base, int index, int /*traceSlot*/) as->passJSSlotAsArg(index, 2); as->passJSSlotAsArg(base, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_storeElement, CallResultDestination::Ignore); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(StoreElement, CallResultDestination::Ignore); } void BaselineJIT::generate_LoadProperty(int name, int /*traceSlot*/) @@ -282,8 +274,7 @@ void BaselineJIT::generate_LoadProperty(int name, int /*traceSlot*/) as->passInt32AsArg(name, 2); as->passAccumulatorAsArg(1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_loadProperty, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(LoadProperty, CallResultDestination::InAccumulator); } void BaselineJIT::generate_GetLookup(int index, int /*traceSlot*/) @@ -293,10 +284,9 @@ void BaselineJIT::generate_GetLookup(int index, int /*traceSlot*/) as->prepareCallWithArgCount(4); as->passInt32AsArg(index, 3); as->passAccumulatorAsArg(2); - as->passEngineAsArg(1); - as->passFunctionAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Helpers::getLookup, CallResultDestination::InAccumulator); - as->checkException(); + as->passFunctionAsArg(1); + as->passEngineAsArg(0); + BASELINEJIT_GENERATE_RUNTIME_CALL(GetLookup, CallResultDestination::InAccumulator); } void BaselineJIT::generate_StoreProperty(int name, int base) @@ -308,8 +298,7 @@ void BaselineJIT::generate_StoreProperty(int name, int base) as->passInt32AsArg(name, 2); as->passJSSlotAsArg(base, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_storeProperty, CallResultDestination::Ignore); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(StoreProperty, CallResultDestination::Ignore); } void BaselineJIT::generate_SetLookup(int index, int base) @@ -318,12 +307,13 @@ void BaselineJIT::generate_SetLookup(int index, int base) STORE_ACC(); as->prepareCallWithArgCount(4); as->passAccumulatorAsArg(3); - as->passJSSlotAsArg(base, 2); - as->passInt32AsArg(index, 1); + as->passInt32AsArg(index, 2); + as->passJSSlotAsArg(base, 1); as->passFunctionAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL((function->isStrict() ? Helpers::setLookupStrict : Helpers::setLookupSloppy), - CallResultDestination::InAccumulator); - as->checkException(); + if (function->isStrict()) + BASELINEJIT_GENERATE_RUNTIME_CALL(SetLookupStrict, CallResultDestination::InAccumulator) + else + BASELINEJIT_GENERATE_RUNTIME_CALL(SetLookupSloppy, CallResultDestination::InAccumulator) } void BaselineJIT::generate_LoadSuperProperty(int property) @@ -333,8 +323,7 @@ void BaselineJIT::generate_LoadSuperProperty(int property) as->prepareCallWithArgCount(2); as->passJSSlotAsArg(property, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_loadSuperProperty, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(LoadSuperProperty, CallResultDestination::InAccumulator); } void BaselineJIT::generate_StoreSuperProperty(int property) @@ -345,8 +334,7 @@ void BaselineJIT::generate_StoreSuperProperty(int property) as->passAccumulatorAsArg(2); as->passJSSlotAsArg(property, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_storeSuperProperty, CallResultDestination::Ignore); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(StoreSuperProperty, CallResultDestination::Ignore); } void BaselineJIT::generate_Yield() @@ -375,8 +363,7 @@ void BaselineJIT::generate_CallValue(int name, int argc, int argv, int /*traceSl as->passJSSlotAsArg(argv, 2); as->passJSSlotAsArg(name, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_callValue, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(CallValue, CallResultDestination::InAccumulator); } void BaselineJIT::generate_CallWithReceiver(int name, int thisObject, int argc, int argv, int /*traceSlot*/) @@ -388,8 +375,7 @@ void BaselineJIT::generate_CallWithReceiver(int name, int thisObject, int argc, as->passJSSlotAsArg(thisObject, 2); as->passJSSlotAsArg(name, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_callWithReceiver, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(CallWithReceiver, CallResultDestination::InAccumulator); } void BaselineJIT::generate_CallProperty(int name, int base, int argc, int argv, int /*traceSlot*/) @@ -401,8 +387,7 @@ void BaselineJIT::generate_CallProperty(int name, int base, int argc, int argv, as->passInt32AsArg(name, 2); as->passJSSlotAsArg(base, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_callProperty, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(CallProperty, CallResultDestination::InAccumulator); } void BaselineJIT::generate_CallPropertyLookup(int lookupIndex, int base, int argc, int argv, int /*traceSlot*/) @@ -414,8 +399,7 @@ void BaselineJIT::generate_CallPropertyLookup(int lookupIndex, int base, int arg as->passInt32AsArg(lookupIndex, 2); as->passJSSlotAsArg(base, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_callPropertyLookup, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(CallPropertyLookup, CallResultDestination::InAccumulator); } void BaselineJIT::generate_CallElement(int base, int index, int argc, int argv, int /*traceSlot*/) @@ -427,8 +411,7 @@ void BaselineJIT::generate_CallElement(int base, int index, int argc, int argv, as->passJSSlotAsArg(index, 2); as->passJSSlotAsArg(base, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_callElement, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(CallElement, CallResultDestination::InAccumulator); } void BaselineJIT::generate_CallName(int name, int argc, int argv, int /*traceSlot*/) @@ -439,8 +422,7 @@ void BaselineJIT::generate_CallName(int name, int argc, int argv, int /*traceSlo as->passJSSlotAsArg(argv, 2); as->passInt32AsArg(name, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_callName, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(CallName, CallResultDestination::InAccumulator); } void BaselineJIT::generate_CallPossiblyDirectEval(int argc, int argv, int /*traceSlot*/) @@ -450,8 +432,7 @@ void BaselineJIT::generate_CallPossiblyDirectEval(int argc, int argv, int /*trac as->passInt32AsArg(argc, 2); as->passJSSlotAsArg(argv, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_callPossiblyDirectEval, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(CallPossiblyDirectEval, CallResultDestination::InAccumulator); } void BaselineJIT::generate_CallGlobalLookup(int index, int argc, int argv, int /*traceSlot*/) @@ -462,8 +443,7 @@ void BaselineJIT::generate_CallGlobalLookup(int index, int argc, int argv, int / as->passJSSlotAsArg(argv, 2); as->passInt32AsArg(index, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_callGlobalLookup, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(CallGlobalLookup, CallResultDestination::InAccumulator); } void BaselineJIT::generate_CallQmlContextPropertyLookup(int index, int argc, int argv, @@ -475,8 +455,7 @@ void BaselineJIT::generate_CallQmlContextPropertyLookup(int index, int argc, int as->passJSSlotAsArg(argv, 2); as->passInt32AsArg(index, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_callQmlContextPropertyLookup, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(CallQmlContextPropertyLookup, CallResultDestination::InAccumulator); } void BaselineJIT::generate_CallWithSpread(int func, int thisObject, int argc, int argv, int /*traceSlot*/) @@ -488,8 +467,7 @@ void BaselineJIT::generate_CallWithSpread(int func, int thisObject, int argc, in as->passJSSlotAsArg(thisObject, 2); as->passJSSlotAsArg(func, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_callWithSpread, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(CallWithSpread, CallResultDestination::InAccumulator); } void BaselineJIT::generate_TailCall(int func, int thisObject, int argc, int argv) @@ -508,8 +486,7 @@ void BaselineJIT::generate_Construct(int func, int argc, int argv) as->passAccumulatorAsArg(2); as->passJSSlotAsArg(func, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_construct, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(Construct, CallResultDestination::InAccumulator); } void BaselineJIT::generate_ConstructWithSpread(int func, int argc, int argv) @@ -522,8 +499,7 @@ void BaselineJIT::generate_ConstructWithSpread(int func, int argc, int argv) as->passAccumulatorAsArg(2); as->passJSSlotAsArg(func, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_constructWithSpread, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(ConstructWithSpread, CallResultDestination::InAccumulator); } void BaselineJIT::generate_SetUnwindHandler(int offset) @@ -556,7 +532,7 @@ void BaselineJIT::generate_ThrowException() as->prepareCallWithArgCount(2); as->passAccumulatorAsArg(1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_throwException, CallResultDestination::Ignore); + BASELINEJIT_GENERATE_RUNTIME_CALL(ThrowException, CallResultDestination::Ignore); as->gotoCatchException(); } @@ -567,8 +543,7 @@ void BaselineJIT::generate_CreateCallContext() { as->prepareCallWithArgCount(1); as->passCppFrameAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(ExecutionContext::newCallContext, CallResultDestination::Ignore); // keeps result in return value register - as->storeHeapObject(CallData::Context); + BASELINEJIT_GENERATE_RUNTIME_CALL(PushCallContext, CallResultDestination::Ignore); } void BaselineJIT::generate_PushCatchContext(int index, int name) { as->pushCatchContext(index, name); } @@ -578,11 +553,9 @@ void BaselineJIT::generate_PushWithContext() STORE_IP(); as->saveAccumulatorInFrame(); as->prepareCallWithArgCount(2); - as->passJSSlotAsArg(0, 1); + as->passJSSlotAsArg(CallData::Accumulator, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_createWithContext, CallResultDestination::Ignore); // keeps result in return value register - as->checkException(); - as->storeHeapObject(CallData::Context); + BASELINEJIT_GENERATE_RUNTIME_CALL(PushWithContext, CallResultDestination::InAccumulator); } void BaselineJIT::generate_PushBlockContext(int index) @@ -590,35 +563,33 @@ void BaselineJIT::generate_PushBlockContext(int index) as->saveAccumulatorInFrame(); as->prepareCallWithArgCount(2); as->passInt32AsArg(index, 1); - as->passJSSlotAsArg(0, 0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Helpers::pushBlockContext, CallResultDestination::Ignore); + as->passEngineAsArg(0); + BASELINEJIT_GENERATE_RUNTIME_CALL(PushBlockContext, CallResultDestination::Ignore); } void BaselineJIT::generate_CloneBlockContext() { as->saveAccumulatorInFrame(); as->prepareCallWithArgCount(1); - as->passJSSlotAsArg(CallData::Context, 0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Helpers::cloneBlockContext, CallResultDestination::Ignore); + as->passEngineAsArg(0); + BASELINEJIT_GENERATE_RUNTIME_CALL(CloneBlockContext, CallResultDestination::Ignore); } void BaselineJIT::generate_PushScriptContext(int index) { as->saveAccumulatorInFrame(); - as->prepareCallWithArgCount(3); - as->passInt32AsArg(index, 2); - as->passEngineAsArg(1); - as->passJSSlotAsArg(0, 0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Helpers::pushScriptContext, CallResultDestination::Ignore); + as->prepareCallWithArgCount(2); + as->passInt32AsArg(index, 1); + as->passEngineAsArg(0); + BASELINEJIT_GENERATE_RUNTIME_CALL(PushScriptContext, CallResultDestination::Ignore); } void BaselineJIT::generate_PopScriptContext() { as->saveAccumulatorInFrame(); - as->prepareCallWithArgCount(2); - as->passEngineAsArg(1); - as->passJSSlotAsArg(0, 0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Helpers::popScriptContext, CallResultDestination::Ignore); + as->prepareCallWithArgCount(1); + as->passEngineAsArg(0); + BASELINEJIT_GENERATE_RUNTIME_CALL(PopScriptContext, CallResultDestination::Ignore); } void BaselineJIT::generate_PopContext() { as->popContext(); } @@ -630,8 +601,7 @@ void BaselineJIT::generate_GetIterator(int iterator) as->passInt32AsArg(iterator, 2); as->passAccumulatorAsArg(1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_getIterator, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(GetIterator, CallResultDestination::InAccumulator); } void BaselineJIT::generate_IteratorNext(int value, int done) @@ -641,9 +611,8 @@ void BaselineJIT::generate_IteratorNext(int value, int done) as->passJSSlotAsArg(value, 2); as->passAccumulatorAsArg(1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_iteratorNext, CallResultDestination::InAccumulator); + BASELINEJIT_GENERATE_RUNTIME_CALL(IteratorNext, CallResultDestination::InAccumulator); as->storeReg(done); - as->checkException(); } void BaselineJIT::generate_IteratorNextForYieldStar(int iterator, int object) @@ -654,8 +623,7 @@ void BaselineJIT::generate_IteratorNextForYieldStar(int iterator, int object) as->passJSSlotAsArg(iterator, 2); as->passAccumulatorAsArg(1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_iteratorNextForYieldStar, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(IteratorNextForYieldStar, CallResultDestination::InAccumulator); } void BaselineJIT::generate_IteratorClose(int done) @@ -665,8 +633,7 @@ void BaselineJIT::generate_IteratorClose(int done) as->passJSSlotAsArg(done, 2); as->passAccumulatorAsArg(1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_iteratorClose, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(IteratorClose, CallResultDestination::InAccumulator); } void BaselineJIT::generate_DestructureRestElement() @@ -675,29 +642,28 @@ void BaselineJIT::generate_DestructureRestElement() as->prepareCallWithArgCount(2); as->passAccumulatorAsArg(1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_destructureRestElement, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(DestructureRestElement, CallResultDestination::InAccumulator); } void BaselineJIT::generate_DeleteProperty(int base, int index) { STORE_IP(); - as->prepareCallWithArgCount(3); - as->passJSSlotAsArg(index, 2); - as->passJSSlotAsArg(base, 1); - as->passFunctionAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Helpers::deleteProperty, CallResultDestination::InAccumulator); - as->checkException(); + as->prepareCallWithArgCount(4); + as->passJSSlotAsArg(index, 3); + as->passJSSlotAsArg(base, 2); + as->passFunctionAsArg(1); + as->passEngineAsArg(0); + BASELINEJIT_GENERATE_RUNTIME_CALL(DeleteProperty, CallResultDestination::InAccumulator); } void BaselineJIT::generate_DeleteName(int name) { STORE_IP(); - as->prepareCallWithArgCount(2); - as->passInt32AsArg(name, 1); - as->passFunctionAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Helpers::deleteName, CallResultDestination::InAccumulator); - as->checkException(); + as->prepareCallWithArgCount(3); + as->passInt32AsArg(name, 2); + as->passFunctionAsArg(1); + as->passEngineAsArg(0); + BASELINEJIT_GENERATE_RUNTIME_CALL(DeleteName, CallResultDestination::InAccumulator); } void BaselineJIT::generate_TypeofName(int name) @@ -705,7 +671,7 @@ void BaselineJIT::generate_TypeofName(int name) as->prepareCallWithArgCount(2); as->passInt32AsArg(name, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_typeofName, CallResultDestination::InAccumulator); + BASELINEJIT_GENERATE_RUNTIME_CALL(TypeofName, CallResultDestination::InAccumulator); } void BaselineJIT::generate_TypeofValue() @@ -714,7 +680,7 @@ void BaselineJIT::generate_TypeofValue() as->prepareCallWithArgCount(2); as->passAccumulatorAsArg(1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_typeofValue, CallResultDestination::InAccumulator); + BASELINEJIT_GENERATE_RUNTIME_CALL(TypeofValue, CallResultDestination::InAccumulator); } void BaselineJIT::generate_DeclareVar(int varName, int isDeletable) @@ -723,7 +689,7 @@ void BaselineJIT::generate_DeclareVar(int varName, int isDeletable) as->passInt32AsArg(varName, 2); as->passInt32AsArg(isDeletable, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_declareVar, CallResultDestination::Ignore); + BASELINEJIT_GENERATE_RUNTIME_CALL(DeclareVar, CallResultDestination::Ignore); } void BaselineJIT::generate_DefineArray(int argc, int args) @@ -732,7 +698,7 @@ void BaselineJIT::generate_DefineArray(int argc, int args) as->passInt32AsArg(argc, 2); as->passJSSlotAsArg(args, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_arrayLiteral, CallResultDestination::InAccumulator); + BASELINEJIT_GENERATE_RUNTIME_CALL(ArrayLiteral, CallResultDestination::InAccumulator); } void BaselineJIT::generate_DefineObjectLiteral(int internalClassId, int argc, int args) @@ -742,7 +708,7 @@ void BaselineJIT::generate_DefineObjectLiteral(int internalClassId, int argc, in as->passJSSlotAsArg(args, 2); as->passInt32AsArg(internalClassId, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_objectLiteral, CallResultDestination::InAccumulator); + BASELINEJIT_GENERATE_RUNTIME_CALL(ObjectLiteral, CallResultDestination::InAccumulator); } void BaselineJIT::generate_CreateClass(int classIndex, int heritage, int computedNames) @@ -752,14 +718,14 @@ void BaselineJIT::generate_CreateClass(int classIndex, int heritage, int compute as->passJSSlotAsArg(heritage, 2); as->passInt32AsArg(classIndex, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_createClass, CallResultDestination::InAccumulator); + BASELINEJIT_GENERATE_RUNTIME_CALL(CreateClass, CallResultDestination::InAccumulator); } void BaselineJIT::generate_CreateMappedArgumentsObject() { as->prepareCallWithArgCount(1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_createMappedArgumentsObject, + BASELINEJIT_GENERATE_RUNTIME_CALL(CreateMappedArgumentsObject, CallResultDestination::InAccumulator); } @@ -767,7 +733,7 @@ void BaselineJIT::generate_CreateUnmappedArgumentsObject() { as->prepareCallWithArgCount(1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_createUnmappedArgumentsObject, + BASELINEJIT_GENERATE_RUNTIME_CALL(CreateUnmappedArgumentsObject, CallResultDestination::InAccumulator); } @@ -776,7 +742,7 @@ void BaselineJIT::generate_CreateRestParameter(int argIndex) as->prepareCallWithArgCount(2); as->passInt32AsArg(argIndex, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_createRestParameter, CallResultDestination::InAccumulator); + BASELINEJIT_GENERATE_RUNTIME_CALL(CreateRestParameter, CallResultDestination::InAccumulator); } void BaselineJIT::generate_ConvertThisToObject() @@ -784,8 +750,8 @@ void BaselineJIT::generate_ConvertThisToObject() as->prepareCallWithArgCount(2); as->passJSSlotAsArg(CallData::This, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Helpers::convertThisToObject, CallResultDestination::Ignore); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(ConvertThisToObject, CallResultDestination::InAccumulator); + as->storeReg(CallData::This); } void BaselineJIT::generate_LoadSuperConstructor() @@ -793,8 +759,7 @@ void BaselineJIT::generate_LoadSuperConstructor() as->prepareCallWithArgCount(2); as->passJSSlotAsArg(CallData::Function, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_loadSuperConstructor, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(LoadSuperConstructor, CallResultDestination::InAccumulator); } void BaselineJIT::generate_ToObject() @@ -803,8 +768,7 @@ void BaselineJIT::generate_ToObject() as->prepareCallWithArgCount(2); as->passAccumulatorAsArg(1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Helpers::toObject, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(ToObject, CallResultDestination::InAccumulator); } @@ -853,8 +817,7 @@ void BaselineJIT::generate_CmpIn(int lhs) as->passAccumulatorAsArg(2); as->passJSSlotAsArg(lhs, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_in, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(In, CallResultDestination::InAccumulator); } void BaselineJIT::generate_CmpInstanceOf(int lhs) @@ -864,12 +827,11 @@ void BaselineJIT::generate_CmpInstanceOf(int lhs) as->passAccumulatorAsArg(2); as->passJSSlotAsArg(lhs, 1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Runtime::method_instanceof, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(Instanceof, CallResultDestination::InAccumulator); } 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(); } @@ -896,8 +858,7 @@ void BaselineJIT::generate_Exp(int lhs) { as->prepareCallWithArgCount(2); as->passAccumulatorAsArg(1); as->passJSSlotAsArg(lhs, 0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Helpers::exp, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(Exp, CallResultDestination::InAccumulator); } void BaselineJIT::generate_Mul(int lhs, int /*traceSlot*/) { as->mul(lhs); } void BaselineJIT::generate_Div(int lhs) { as->div(lhs); } @@ -929,8 +890,7 @@ void BaselineJIT::generate_ThrowOnNullOrUndefined() as->prepareCallWithArgCount(2); as->passAccumulatorAsArg(1); as->passEngineAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(Helpers::throwOnNullOrUndefined, CallResultDestination::Ignore); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(ThrowOnNullOrUndefined, CallResultDestination::Ignore); } void BaselineJIT::generate_GetTemplateObject(int index) @@ -939,14 +899,14 @@ void BaselineJIT::generate_GetTemplateObject(int index) as->prepareCallWithArgCount(2); as->passInt32AsArg(index, 1); as->passFunctionAsArg(0); - BASELINEJIT_GENERATE_RUNTIME_CALL(RuntimeHelpers::getTemplateObject, CallResultDestination::InAccumulator); - as->checkException(); + BASELINEJIT_GENERATE_RUNTIME_CALL(GetTemplateObject, CallResultDestination::InAccumulator); } -void BaselineJIT::startInstruction(Instr::Type /*instr*/) +ByteCodeHandler::Verdict BaselineJIT::startInstruction(Instr::Type /*instr*/) { if (labels.contains(currentInstructionOffset())) as->addLabel(currentInstructionOffset()); + return ProcessInstruction; } void BaselineJIT::endInstruction(Instr::Type instr) diff --git a/src/qml/jit/qv4baselinejit_p.h b/src/qml/jit/qv4baselinejit_p.h index 10c89bc74b..37ab37eac2 100644 --- a/src/qml/jit/qv4baselinejit_p.h +++ b/src/qml/jit/qv4baselinejit_p.h @@ -179,7 +179,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; @@ -206,7 +206,7 @@ public: void generate_ThrowOnNullOrUndefined() override; void generate_GetTemplateObject(int index) override; - void startInstruction(Moth::Instr::Type instr) override; + Verdict startInstruction(Moth::Instr::Type instr) override; void endInstruction(Moth::Instr::Type instr) override; private: 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 <QtCore/qloggingcategory.h> + +#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<MIBlock *, 32> 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 <QtCore/qstack.h> + +#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<MIBlock *> postponed; + + WorkForGroup(MIBlock *group = nullptr) : group(group) {} + }; + WorkForGroup currentGroup; + QStack<WorkForGroup> postponedGroups; + std::vector<MIBlock *> 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<MIBlock *, MIBlock *> 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<MIBlock *> &scheduledBlockSequence() const + { return sequence; } + + QHash<MIBlock *, MIBlock *> 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 <QtCore/qloggingcategory.h> +#include <QtCore/qbuffer.h> + +#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<MIBlock *> DominatorTree::calculateDFNodeIterOrder() const +{ + std::vector<int> depths = calculateNodeDepths(); + std::vector<MIBlock *> 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<int> DominatorTree::calculateNodeDepths() const +{ + std::vector<int> 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<int> &nodeDepths) const +{ + std::vector<int> 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<DFSTodo> 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<MIBlock::Index>(bbCount, MIBlock::InvalidIndex); + m_data->parent = std::vector<MIBlock::Index>(bbCount, MIBlock::InvalidIndex); + m_data->dfnum = std::vector<unsigned>(bbCount, 0); + m_data->semi = std::vector<MIBlock::Index>(bbCount, MIBlock::InvalidIndex); + m_data->ancestor = std::vector<MIBlock::Index>(bbCount, MIBlock::InvalidIndex); + m_idom = std::vector<MIBlock::Index>(bbCount, MIBlock::InvalidIndex); + m_data->samedom = std::vector<MIBlock::Index>(bbCount, MIBlock::InvalidIndex); + m_data->best = std::vector<MIBlock::Index>(bbCount, MIBlock::InvalidIndex); + + QHash<MIBlock::Index, std::vector<MIBlock::Index>> bucket; + bucket.reserve(int(bbCount)); + + dfs(m_function->block(0)->index()); + + std::vector<MIBlock::Index> 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<MIBlock::Index> &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<int>(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<MIBlock::Index> children; + std::vector<MIBlock::Index> todo; + }; + + MIFunction *function = domTree.function(); + m_df.resize(function->blockCount()); + + // compute children of each node in the dominator tree + std::vector<std::vector<MIBlock::Index> > 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<NodeProgress> nodeStatus; + nodeStatus.resize(function->blockCount()); + std::vector<MIBlock::Index> 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<MIBlock *> calculateDFNodeIterOrder() const; + + std::vector<int> calculateNodeDepths() const; + +private: // functions + int calculateNodeDepth(MIBlock::Index nodeIdx, std::vector<int> &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<MIBlock::Index> &worklist); + +private: // data + struct Data { + std::vector<unsigned> dfnum; // MIBlock index -> dfnum + std::vector<MIBlock::Index> vertex; + std::vector<MIBlock::Index> parent; // MIBlock index -> parent MIBlock index + std::vector<MIBlock::Index> ancestor; // MIBlock index -> ancestor MIBlock index + std::vector<MIBlock::Index> best; // MIBlock index -> best MIBlock index + std::vector<MIBlock::Index> semi; // MIBlock index -> semi dominator MIBlock index + std::vector<MIBlock::Index> samedom; // MIBlock index -> same dominator MIBlock index + unsigned size = 0; + }; + + MIFunction *m_function; + QScopedPointer<Data> m_data; + std::vector<MIBlock::Index> 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<MIBlockSet> m_df; // MIBlock index -> dominator frontier +}; + +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4DOMTREE_P_H diff --git a/src/qml/jit/qv4graph.cpp b/src/qml/jit/qv4graph.cpp new file mode 100644 index 0000000000..4025ceb993 --- /dev/null +++ b/src/qml/jit/qv4graph.cpp @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** 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 "qv4graph_p.h" +#include "qv4operation_p.h" + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE +namespace QV4 { +namespace IR { + +Graph *Graph::create(Function *function) +{ + auto storage = function->pool()->allocate(sizeof(Graph)); + auto g = new (storage) Graph(function); + g->m_undefinedNode = g->createNode(g->opBuilder()->get<Meta::Undefined>()); + g->m_emptyNode = g->createNode(g->opBuilder()->get<Meta::Empty>()); + g->m_nullNode = g->createNode(g->opBuilder()->getConstant(QV4::Value::nullValue())); + g->m_trueNode = g->createNode(g->opBuilder()->getConstant(QV4::Value::fromBoolean(true))); + g->m_falseNode = g->createNode(g->opBuilder()->getConstant(QV4::Value::fromBoolean(false))); + return g; +} + +Graph::MemoryPool *Graph::pool() const +{ + return m_function->pool(); +} + +Node *Graph::createNode(const Operation *op, Node *const operands[], size_t opCount, + bool incomplete) +{ + return Node::create(pool(), m_nextNodeId++, op, opCount, operands, incomplete); +} + +Node *Graph::createConstantBoolNode(bool value) +{ + return createNode(opBuilder()->getConstant(Primitive::fromBoolean(value))); +} + +Node *Graph::createConstantIntNode(int value) +{ + return createNode(opBuilder()->getConstant(Primitive::fromInt32(value))); +} + +Graph::Graph(Function *function) + : m_function(function) + , m_opBuilder(OperationBuilder::create(pool())) +{} + +Node *Graph::createConstantHeapNode(Heap::Base *heap) +{ + return createNode(opBuilder()->getConstant(Primitive::fromHeapObject(heap))); +} + +void Graph::addEndInput(Node *n) +{ + if (m_endNode) { + auto newEnd = m_opBuilder->getEnd(m_endNode->operation()->controlInputCount() + 1); + m_endNode->setOperation(newEnd); + m_endNode->addInput(m_function->pool(), n); + } +} + +} // IR namespace +} // QV4 namespace +QT_END_NAMESPACE diff --git a/src/qml/jit/qv4graph_p.h b/src/qml/jit/qv4graph_p.h new file mode 100644 index 0000000000..4706399c94 --- /dev/null +++ b/src/qml/jit/qv4graph_p.h @@ -0,0 +1,146 @@ +/**************************************************************************** +** +** 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 QV4GRAPH_P_H +#define QV4GRAPH_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 <private/qqmljsmemorypool_p.h> +#include <private/qv4global_p.h> +#include <private/qv4node_p.h> + +#include <array> + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace IR { + +class Function; +class Operation; +class OperationBuilder; + +class Graph final +{ + Q_DISABLE_COPY_MOVE(Graph) + +public: + using MemoryPool = QQmlJS::MemoryPool; + +public: + static Graph *create(Function *function); + ~Graph() = delete; + + MemoryPool *pool() const; + OperationBuilder *opBuilder() const + { return m_opBuilder; } + + Node *createNode(const Operation *op, Node * const operands[] = nullptr, size_t opCount = 0, + bool incomplete = false); + template <typename... Nodes> + Node *createNode(Operation *op, Nodes*... nodes) { + std::array<Node *, sizeof...(nodes)> nodesArray {{ nodes... }}; + return createNode(op, nodesArray.data(), nodesArray.size()); + } + Node *createConstantBoolNode(bool value); + Node *createConstantIntNode(int value); + Node *createConstantHeapNode(Heap::Base *heap); + + Node *undefinedNode() const { return m_undefinedNode; } + Node *emptyNode() const { return m_emptyNode; } + Node *nullNode() const { return m_nullNode; } + Node *trueConstant() const { return m_trueNode; } + Node *falseConstant() const { return m_falseNode; } + + Node *startNode() const { return m_startNode; } + Node *engineNode() const { return m_engineNode; } + Node *functionNode() const { return m_functionNode; } + Node *cppFrameNode() const { return m_cppFrameNode; } + Node *endNode() const { return m_endNode; } + Node *initialFrameState() const { return m_initialFrameState; } + void setStartNode(Node *n) { m_startNode = n; } + void setEngineNode(Node *n) { m_engineNode = n; } + void setFunctionNode(Node *n) { m_functionNode = n; } + void setCppFrameNode(Node *n) { m_cppFrameNode = n; } + void setEndNode(Node *n) { m_endNode = n; } + void setInitialFrameState(Node *n) { m_initialFrameState = n; } + + unsigned nodeCount() const + { return unsigned(m_nextNodeId); } + + void addEndInput(Node *n); + +private: // types and methods + Graph(Function *function); + +private: // fields + Function *m_function; + OperationBuilder *m_opBuilder; + Node::Id m_nextNodeId = 0; + Node *m_undefinedNode = nullptr; + Node *m_emptyNode = nullptr; + Node *m_nullNode = nullptr; + Node *m_trueNode = nullptr; + Node *m_falseNode = nullptr; + Node *m_startNode = nullptr; + Node *m_engineNode = nullptr; + Node *m_functionNode = nullptr; + Node *m_cppFrameNode = nullptr; + Node *m_endNode = nullptr; + Node *m_initialFrameState = nullptr; +}; + +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4GRAPH_P_H diff --git a/src/qml/jit/qv4graphbuilder.cpp b/src/qml/jit/qv4graphbuilder.cpp new file mode 100644 index 0000000000..2c073701ee --- /dev/null +++ b/src/qml/jit/qv4graphbuilder.cpp @@ -0,0 +1,1683 @@ +/**************************************************************************** +** +** 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 <QtCore/qloggingcategory.h> + +#include "qv4graphbuilder_p.h" +#include "qv4function_p.h" +#include "qv4lookup_p.h" +#include "qv4stackframe_p.h" +#include "qv4operation_p.h" + +QT_BEGIN_NAMESPACE +namespace QV4 { +namespace IR { + +Q_LOGGING_CATEGORY(lcIRGraphBuilder, "qt.v4.ir.graphbuilder") + +using MemoryPool = QQmlJS::MemoryPool; + +namespace { +template <typename T, size_t N> +char (&ArraySizeHelper(T (&array)[N]))[N]; +template <typename T, size_t N> +char (&ArraySizeHelper(const T (&array)[N]))[N]; + +template <typename Array> +inline size_t arraySize(const Array &array) +{ + Q_UNUSED(array); // for MSVC + return sizeof(ArraySizeHelper(array)); +} +} // anonymous namespace + +class GraphBuilder::InterpreterEnvironment +{ +public: + struct FrameState: public QQmlJS::FixedPoolArray<Node *> + { + FrameState(MemoryPool *pool, int totalSlotCount) + : FixedPoolArray(pool, totalSlotCount) + {} + + Node *&unwindHandlerOffset() + { return at(size() - 1); } + + static FrameState *create(MemoryPool *pool, int jsSlotCount) + { + auto totalSlotCount = jsSlotCount; + auto fs = pool->New<FrameState>(pool, totalSlotCount); + return fs; + } + + static FrameState *clone(MemoryPool *pool, FrameState *other) + { + FrameState *fs = create(pool, other->size()); + + for (int i = 0, ei = other->size(); i != ei; ++i) + fs->at(i) = other->at(i); + + return fs; + } + }; + +public: + InterpreterEnvironment(GraphBuilder *graphBuilder, Node *controlDependency) + : m_graphBuilder(graphBuilder) + , m_effectDependency(controlDependency) + , m_controlDependency(controlDependency) + , m_currentFrame(nullptr) + {} + + void createEnvironment() + { + Function *f = function(); + QV4::Function *v4Function = f->v4Function(); + const size_t nRegisters = v4Function->compiledFunction->nRegisters; + + // 1 extra slot for the unwindHandlerOffset + m_currentFrame = FrameState::create(graph()->pool(), int(nRegisters + 1)); + } + + void setupStartEnvironment() + { + Function *f = function(); + QV4::Function *v4Function = f->v4Function(); + const size_t nFormals = v4Function->compiledFunction->nFormals; + const size_t nRegisters = v4Function->compiledFunction->nRegisters; + + createEnvironment(); + + Node *startNode = graph()->startNode(); + auto opB = opBuilder(); + auto create = [&](int index, const char *name) { + m_currentFrame->at(index) = graph()->createNode( + opB->getParam(index, f->addString(QLatin1String(name))), startNode); + }; + create(0, "%function"); + create(1, "%context"); + create(2, "%acc"); + create(3, "%this"); + create(4, "%newTarget"); + create(5, "%argc"); + const quint32_le *formalNameIdx = v4Function->compiledFunction->formalsTable(); + for (size_t i = 0; i < nFormals; ++i, ++formalNameIdx) { + const int slot = int(CallData::HeaderSize() + i); + Q_ASSERT(*formalNameIdx <= quint32(std::numeric_limits<int>::max())); + auto op = opB->getParam( + slot, + f->addString(v4Function->compilationUnit->stringAt(int(*formalNameIdx)))); + Node *argNode = graph()->createNode(op, startNode); + m_currentFrame->at(slot) = argNode; + } + Node *undefinedNode = graph()->undefinedNode(); + Node *emptyNode = graph()->emptyNode(); + const auto firstDeadZoneRegister + = v4Function->compiledFunction->firstTemporalDeadZoneRegister; + const auto registerDeadZoneSize + = v4Function->compiledFunction->sizeOfRegisterTemporalDeadZone; + for (size_t i = CallData::HeaderSize() + nFormals; i < nRegisters; ++i) { + const bool isDead = i >= firstDeadZoneRegister + && i < size_t(firstDeadZoneRegister + registerDeadZoneSize); + m_currentFrame->at(int(i)) = isDead ? emptyNode : undefinedNode; + } + setUnwindHandlerOffset(0); + } + + Function *function() const { return m_graphBuilder->function(); } + Graph *graph() const { return function()->graph(); } + OperationBuilder *opBuilder() const { return graph()->opBuilder(); } + GraphBuilder *graphBuilder() const { return m_graphBuilder; } + + Node *bindAcc(Node *node) + { + bindNodeToSlot(node, CallData::Accumulator); + return node; + } + + Node *accumulator() const + { return slot(CallData::Accumulator); } + + Node *bindNodeToSlot(Node *node, int slot) + { + m_currentFrame->at(size_t(slot)) = node; + return node; + } + + Node *slot(int slot) const + { return m_currentFrame->at(slot); } + + int slotCount() const + { return m_currentFrame->size(); } + + Node *effectDependency() const + { return m_effectDependency; } + + void setEffectDependency(Node *newNode) + { m_effectDependency = newNode; } + + Node *controlDependency() const + { return m_controlDependency; } + + void setControlDependency(Node *newNode) + { m_controlDependency = newNode; } + + Node *createFrameState() + { + return graph()->createNode(graphBuilder()->opBuilder()->getFrameState(slotCount()), + m_currentFrame->begin(), slotCount()); + } + + Node *merge(InterpreterEnvironment *other); + + InterpreterEnvironment *copy() const + { + auto *newEnv = graph()->pool()->New<InterpreterEnvironment>(graphBuilder(), + controlDependency()); + newEnv->setEffectDependency(effectDependency()); + newEnv->m_currentFrame = FrameState::clone(graph()->pool(), m_currentFrame); + return newEnv; + } + + int unwindHandlerOffset() const + { + auto uhOp = m_currentFrame->unwindHandlerOffset()->operation(); + Q_ASSERT(uhOp->kind() == Meta::Constant); + return ConstantPayload::get(*uhOp)->value().int_32(); + } + + void setUnwindHandlerOffset(int newOffset) + { m_currentFrame->unwindHandlerOffset() = graphBuilder()->createConstant(newOffset); } + + FrameState *frameState() const + { return m_currentFrame; } + +private: + GraphBuilder *m_graphBuilder; + Node *m_effectDependency; + Node *m_controlDependency; + FrameState *m_currentFrame; +}; + +namespace { +class InterpreterSubEnvironment final +{ + Q_DISABLE_COPY_MOVE(InterpreterSubEnvironment) + +public: + explicit InterpreterSubEnvironment(GraphBuilder *builder) + : m_builder(builder) + , m_parent(builder->env()->copy()) + {} + + ~InterpreterSubEnvironment() + { m_builder->setEnv(m_parent); } + +private: + GraphBuilder *m_builder; + GraphBuilder::InterpreterEnvironment *m_parent; +}; +} // anonymous namespace + +Node *GraphBuilder::InterpreterEnvironment::merge(InterpreterEnvironment *other) +{ + Q_ASSERT(m_currentFrame->size() == other->m_currentFrame->size()); + + auto gb = graphBuilder(); + Node *mergedControl = gb->mergeControl(controlDependency(), other->controlDependency()); + setControlDependency(mergedControl); + setEffectDependency(gb->mergeEffect(effectDependency(), other->effectDependency(), mergedControl)); + + // insert/update phi nodes, but not for the unwind handler: + for (int i = 0, ei = m_currentFrame->size() - 1; i != ei; ++i) { + //### use lifeness info to trim this! + m_currentFrame->at(i) = gb->mergeValue(m_currentFrame->at(i), + other->m_currentFrame->at(i), + mergedControl); + } + Q_ASSERT(unwindHandlerOffset() >= 0); // specifically: don't crash + return mergedControl; +} + +void GraphBuilder::buildGraph(IR::Function *function) +{ + const char *code = function->v4Function()->codeData; + uint len = function->v4Function()->compiledFunction->codeSize; + + GraphBuilder builder(function); + builder.startGraph(); + + InterpreterEnvironment initial(&builder, function->graph()->startNode()); + initial.setupStartEnvironment(); + builder.setEnv(&initial); + builder.graph()->setInitialFrameState(initial.createFrameState()); + builder.decode(code, len); + builder.endGraph(); +}; + +GraphBuilder::GraphBuilder(IR::Function *function) + : m_func(function) + , m_graph(function->graph()) + , m_currentEnv(nullptr) +{ + for (unsigned i = 0, ei = m_func->v4Function()->compiledFunction->nLabelInfos; i != ei; ++i) { + unsigned label = m_func->v4Function()->compiledFunction->labelInfoTable()[i]; + m_labelInfos.emplace_back(label); + if (lcIRGraphBuilder().isDebugEnabled()) { + const LabelInfo &li = m_labelInfos.back(); + qCDebug(lcIRGraphBuilder) << "Loop start at" << li.labelOffset; + } + } +} + +void GraphBuilder::startGraph() +{ + size_t nValuesOut = 1 + CallData::HeaderSize() + + m_func->v4Function()->compiledFunction->nFormals; + Node *start = m_graph->createNode(opBuilder()->getStart(uint16_t(nValuesOut)), nullptr, 0); + m_func->nodeInfo(start)->setBytecodeOffsets(0, 0); + m_graph->setStartNode(start); + m_graph->setEngineNode(m_graph->createNode(opBuilder()->get<Meta::Engine>(), &start, 1)); + auto frame = m_graph->createNode(opBuilder()->get<Meta::CppFrame>(), &start, 1); + m_graph->setCppFrameNode(frame); + m_graph->setFunctionNode(m_graph->createNode(opBuilder()->get<Meta::Function>(), + &frame, 1)); +} + +void GraphBuilder::endGraph() +{ + const auto inputCount = uint16_t(m_exitControls.size()); + Node **inputs = &m_exitControls.front(); + Q_ASSERT(m_graph->endNode() == nullptr); + m_graph->setEndNode(m_graph->createNode(opBuilder()->getEnd(inputCount), inputs, inputCount)); +} + +Node *GraphBuilder::bindAcc(Node *n) +{ + return env()->bindAcc(n); +} + +/* IMPORTANT!!! + * + * This might change the success environment, so don't call: + * env()->bindAcc(createNode(...)) + * because the binding should only happen on success, but the call to env() will get the + * environment from *before* the new success environment was created. Instead, do: + * bindAcc(createNode(....)) + */ +Node *GraphBuilder::createAndLinkNode(Operation *op, Node *operands[], size_t opCount, + bool incomplete) +{ + Q_ASSERT(op->effectInputCount() < 2); + Q_ASSERT(op->controlInputCount() < 2); + + QVarLengthArray<Node *, 32> inputs(static_cast<int>(opCount)); + std::copy_n(operands, opCount, inputs.data()); + + if (op->effectInputCount() == 1) + inputs.append(env()->effectDependency()); + if (op->controlInputCount() == 1) + inputs.append(env()->controlDependency()); + if (op->hasFrameStateInput()) + inputs.append(env()->createFrameState()); + + Node *node = m_graph->createNode(op, inputs.data(), inputs.size(), incomplete); + + if (op->needsBytecodeOffsets()) { + m_func->nodeInfo(node)->setBytecodeOffsets(currentInstructionOffset(), + nextInstructionOffset()); + } + + if (op->effectOutputCount() > 0) + env()->setEffectDependency(node); + if (op->controlOutputCount() > 0) + env()->setControlDependency(node); + + if (op->canThrow() && env()->unwindHandlerOffset()) { + InterpreterSubEnvironment successEnv(this); + Node *control = env()->controlDependency(); + control = m_graph->createNode(opBuilder()->get<Meta::OnException>(), &control, 1); + env()->setControlDependency(control); + auto unwindHandlerOffset = env()->unwindHandlerOffset(); + mergeIntoSuccessor(unwindHandlerOffset); + } + + return node; +} + +Node *GraphBuilder::createNode(Operation *op, bool incomplete) +{ + return createAndLinkNode(op, nullptr, 0, incomplete); +} + +Node *GraphBuilder::createNode(Operation *op, Node *n1) +{ + Node *buf[] = { n1 }; + return createAndLinkNode(op, buf, arraySize(buf)); +} + +Node *GraphBuilder::createNode(Operation *op, Node *n1, Node *n2) +{ + Node *buf[] = { n1, n2 }; + return createAndLinkNode(op, buf, arraySize(buf)); +} + +Node *GraphBuilder::createNode(Operation *op, Node *n1, Node *n2, Node *n3) +{ + Node *buf[] = { n1, n2, n3 }; + return createAndLinkNode(op, buf, arraySize(buf)); +} + +Node *GraphBuilder::createNode(Operation *op, Node *n1, Node *n2, Node *n3, Node *n4) +{ + Node *buf[] = { n1, n2, n3, n4 }; + return createAndLinkNode(op, buf, arraySize(buf)); +} + +Node *GraphBuilder::createRegion(unsigned nControlInputs) +{ + return createNode(opBuilder()->getRegion(nControlInputs), true); +} + +Node *GraphBuilder::createIfTrue() +{ + return createNode(opBuilder()->get<Meta::IfTrue>()); +} + +Node *GraphBuilder::createIfFalse() +{ + return createNode(opBuilder()->get<Meta::IfFalse>()); +} + +Node *GraphBuilder::createConstant(int v) +{ + return m_graph->createNode(opBuilder()->getConstant(Primitive::fromInt32(v))); +} + +Node *GraphBuilder::createPhi(unsigned nInputs, Node *input, Node *control) +{ + auto phiOp = opBuilder()->getPhi(nInputs); + QVarLengthArray<Node *, 32> buffer(int(nInputs + 1)); + std::fill_n(buffer.data(), nInputs, input); + buffer[int(nInputs)] = control; + return m_graph->createNode(phiOp, buffer.data(), nInputs + 1, true); +} + +Node *GraphBuilder::createEffectPhi(unsigned nInputs, Node *input, Node *control) +{ + auto phiOp = opBuilder()->getEffectPhi(nInputs); + QVarLengthArray<Node *, 32> buffer(int(nInputs + 1)); + std::fill_n(buffer.data(), nInputs, input); + buffer[int(nInputs)] = control; + return m_graph->createNode(phiOp, buffer.data(), nInputs + 1, true); +} + +Node *GraphBuilder::createHandleUnwind(int offset) +{ + return createNode(opBuilder()->getHandleUnwind(offset)); +} + +Node *GraphBuilder::mergeControl(Node *c1, Node *c2) +{ + if (c1->operation()->kind() == Meta::Region) { + const unsigned nInputs = c1->operation()->controlInputCount() + 1; + c1->addInput(m_graph->pool(), c2); + c1->setOperation(opBuilder()->getRegion(nInputs)); + return c1; + } + auto op = opBuilder()->getRegion(2); + Node *inputs[] = { c1, c2 }; + return m_graph->createNode(op, inputs, 2); +} + +Node *GraphBuilder::mergeEffect(Node *e1, Node *e2, Node *control) +{ + const unsigned nInputs = control->operation()->controlInputCount(); + if (e1->operation()->kind() == Meta::EffectPhi && e1->controlInput() == control) { + e1->insertInput(m_graph->pool(), nInputs - 1, e2); + e1->setOperation(opBuilder()->getEffectPhi(nInputs)); + return e1; + } + + if (e1 != e2) { + Node *phi = createEffectPhi(nInputs, e1, control); + phi->replaceInput(nInputs - 1, e2); + return phi; + } + + return e1; +} + +Node *GraphBuilder::mergeValue(Node *v1, Node *v2, Node *control) +{ + const unsigned nInputs = control->operation()->controlInputCount(); + if (v1->operation()->kind() == Meta::Phi && v1->controlInput() == control) { + v1->insertInput(m_graph->pool(), nInputs - 1, v2); + v1->setOperation(opBuilder()->getPhi(nInputs)); + return v1; + } + + if (v1 != v2) { + Node *phi = createPhi(nInputs, v1, control); + phi->replaceInput(nInputs - 1, v2); + return phi; + } + + return v1; +} + +Node *GraphBuilder::createToBoolean(Node *input) +{ + return createNode(opBuilder()->get<Meta::ToBoolean>(), input); +} + +void GraphBuilder::populate(VarArgNodes &args, int argc, int argv) +{ + for (int i = 0; i < argc; ++i) + args.append(env()->slot(argv + i)); + Q_ASSERT(argc >= 0 && argc <= std::numeric_limits<uint16_t>::max()); +} + +void GraphBuilder::queueFunctionExit(Node *exitNode) +{ + m_exitControls.push_back(exitNode); + setEnv(nullptr); +} + +Node *GraphBuilder::mergeIntoSuccessor(int offset) +{ + InterpreterEnvironment *&successorEnvironment = m_envForOffset[offset]; + + Node *region = nullptr; + if (successorEnvironment == nullptr) { + region = createRegion(1); + successorEnvironment = env(); + } else { + // Merge any values which are live coming into the successor. + region = successorEnvironment->merge(env()); + } + setEnv(nullptr); + return region; +} + +const GraphBuilder::LabelInfo *GraphBuilder::labelInfoAt(unsigned offset) const +{ + for (const LabelInfo &li : m_labelInfos) { + if (li.labelOffset == offset) + return &li; + } + return nullptr; +} + +const GraphBuilder::LabelInfo *GraphBuilder::isLoopStart(unsigned offset) const +{ + if (auto li = labelInfoAt(offset)) { + //### in the future, check if this is a loop start, or some other label + return li; + } + + return nullptr; +} + +void GraphBuilder::handleLoopStart(const LabelInfo &labelInfo) +{ + Q_ASSERT(env() != nullptr); + + // We unconditionally insert a region node with phi nodes here. Now there might already be + // such a node, (e.g. the region after an if-then-else), but for simplicity we ignore that. + // A subsequent pass will fold/remove chains of Region nodes. + //### FIXME: add a DCE pass + + const auto offset = int(labelInfo.labelOffset); + Node *control = createRegion(1); + env()->setControlDependency(control); + Node *effect = createEffectPhi(1, env()->effectDependency(), control); + env()->setEffectDependency(effect); + + // insert/update phi nodes, but not for the unwind handler: + for (int i = 0, ei = env()->slotCount() - 1; i != ei; ++i) { + //### use lifeness info to trim this further! + if (i == CallData::Accumulator) + continue; // should never be alive on loop entry + env()->bindNodeToSlot(createPhi(1, env()->slot(i), control), i); + } + + m_envForOffset.insert(offset, env()->copy()); +} + +void GraphBuilder::startUnwinding() +{ + if (int target = env()->unwindHandlerOffset()) { + mergeIntoSuccessor(target); + } else { + bindAcc(graph()->undefinedNode()); + generate_Ret(); + } +} + +void GraphBuilder::generate_Ret() +{ + Node* control = createNode(opBuilder()->get<Meta::Return>(), env()->accumulator()); + queueFunctionExit(control); +} + +void GraphBuilder::generate_Debug() { Q_UNREACHABLE(); } + +void GraphBuilder::generate_LoadConst(int index) +{ + auto func = function()->v4Function(); + Value v = func->compilationUnit->constants[index]; + bindAcc(createNode(opBuilder()->getConstant(v))); +} + +void GraphBuilder::generate_LoadZero() +{ + bindAcc(createConstant(0)); +} + +void GraphBuilder::generate_LoadTrue() +{ + bindAcc(m_graph->trueConstant()); +} + +void GraphBuilder::generate_LoadFalse() +{ + bindAcc(m_graph->falseConstant()); +} + +void GraphBuilder::generate_LoadNull() +{ + bindAcc(m_graph->nullNode()); +} + +void GraphBuilder::generate_LoadUndefined() +{ + bindAcc(m_graph->undefinedNode()); +} + +void GraphBuilder::generate_LoadInt(int value) +{ + bindAcc(m_graph->createNode(opBuilder()->getConstant(Primitive::fromInt32(value)))); +} + +void GraphBuilder::generate_MoveConst(int constIndex, int destTemp) +{ + auto func = function()->v4Function(); + Value v = func->compilationUnit->constants[constIndex]; + env()->bindNodeToSlot(createNode(opBuilder()->getConstant(v)), destTemp); +} + +void GraphBuilder::generate_LoadReg(int reg) +{ + bindAcc(env()->slot(reg)); +} + +void GraphBuilder::generate_StoreReg(int reg) +{ + Node *n = env()->accumulator(); + if (reg == CallData::This) + n = createNode(opBuilder()->get<Meta::StoreThis>(), n); + env()->bindNodeToSlot(n, reg); +} + +void GraphBuilder::generate_MoveReg(int srcReg, int destReg) +{ + env()->bindNodeToSlot(env()->slot(srcReg), destReg); +} + +void GraphBuilder::generate_LoadImport(int index) +{ + auto func = function()->v4Function(); + Value v = *func->compilationUnit->imports[index]; + bindAcc(createNode(opBuilder()->getConstant(v))); +} + +void GraphBuilder::generate_LoadLocal(int index, int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::ScopedLoad>(), + createConstant(0), + createConstant(index))); +} + +void GraphBuilder::generate_StoreLocal(int index) +{ + createNode(opBuilder()->get<Meta::ScopedStore>(), + createConstant(0), + createConstant(index), + env()->accumulator()); +} + +void GraphBuilder::generate_LoadScopedLocal(int scope, int index, int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::ScopedLoad>(), + createConstant(scope), + createConstant(index))); +} + +void GraphBuilder::generate_StoreScopedLocal(int scope, int index) +{ + createNode(opBuilder()->get<Meta::ScopedStore>(), + createConstant(scope), + createConstant(index), + env()->accumulator()); +} + +void GraphBuilder::generate_LoadRuntimeString(int stringId) +{ + auto func = function()->v4Function(); + Value v = Value::fromHeapObject(func->compilationUnit->runtimeStrings[stringId]); + bindAcc(createNode(opBuilder()->getConstant(v))); +} + +void GraphBuilder::generate_MoveRegExp(int regExpId, int destReg) +{ + env()->bindNodeToSlot(createNode(opBuilder()->get<Meta::LoadRegExp>(), + createConstant(regExpId)), destReg); +} + +void GraphBuilder::generate_LoadClosure(int value) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSLoadClosure>(), + createConstant(value))); +} + +void GraphBuilder::generate_LoadName(int name, int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSLoadName>(), + createConstant(name))); +} + +void GraphBuilder::generate_LoadGlobalLookup(int index, int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSLoadGlobalLookup>(), createConstant(index))); +} + +void GraphBuilder::generate_StoreNameSloppy(int name) +{ + createNode(opBuilder()->get<Meta::JSStoreNameSloppy>(), createConstant(name), env()->accumulator()); +} + +void GraphBuilder::generate_StoreNameStrict(int name) +{ + createNode(opBuilder()->get<Meta::JSStoreNameStrict>(), createConstant(name), env()->accumulator()); +} + +void GraphBuilder::generate_LoadElement(int base, int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSLoadElement>(), + env()->slot(base), + env()->accumulator())); +} + +void GraphBuilder::generate_StoreElement(int base, int index, int /*traceSlot*/) +{ + createNode(opBuilder()->get<Meta::JSStoreElement>(), + env()->slot(base), + env()->slot(index), + env()->accumulator()); +} + +void GraphBuilder::generate_LoadProperty(int name, int /*traceSlot*/) +{ + Node *n = createNode(opBuilder()->get<Meta::JSLoadProperty>(), + env()->accumulator(), + createConstant(name)); + bindAcc(n); +} + +void GraphBuilder::generate_GetLookup(int index, int /*traceSlot*/) +{ + Node *n = createNode(opBuilder()->get<Meta::JSGetLookup>(), + env()->accumulator(), + createConstant(index)); + bindAcc(n); +} + +void GraphBuilder::generate_StoreProperty(int name, int base) +{ + createNode(opBuilder()->get<Meta::JSStoreProperty>(), + env()->slot(base), + createConstant(name), + env()->accumulator()); +} + +void GraphBuilder::generate_SetLookup(int index, int base) +{ + + function()->v4Function()->isStrict() + ? createNode(opBuilder()->get<Meta::JSSetLookupStrict>(), env()->slot(base), + createConstant(index), env()->accumulator()) + : createNode(opBuilder()->get<Meta::JSSetLookupSloppy>(), env()->slot(base), + createConstant(index), env()->accumulator()); +} + +void GraphBuilder::generate_LoadSuperProperty(int property) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSLoadSuperProperty>(), + env()->slot(property))); +} + +void GraphBuilder::generate_StoreSuperProperty(int property) +{ + createNode(opBuilder()->get<Meta::JSStoreSuperProperty>(), + createConstant(property), + env()->accumulator()); +} + +void GraphBuilder::generate_LoadQmlContextPropertyLookup(int propertyIndex, int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::QMLLoadQmlContextPropertyLookup>(), + createConstant(propertyIndex))); +} + +void GraphBuilder::generate_Yield() { Q_UNREACHABLE(); } +void GraphBuilder::generate_YieldStar() { Q_UNREACHABLE(); } +void GraphBuilder::generate_Resume(int /*offset*/) { Q_UNREACHABLE(); } + +void GraphBuilder::finalizeCall(Operation::Kind kind, VarArgNodes &args, int argc, int argv) +{ + populate(args, argc, argv); + bindAcc(createAndLinkNode(opBuilder()->getJSVarArgsCall(kind, uint16_t(args.size())), + args.data(), size_t(args.size()))); +} + +void GraphBuilder::generate_CallValue(int name, int argc, int argv, int /*traceSlot*/) +{ + VarArgNodes args; + args.append(env()->slot(name)); + finalizeCall(Meta::JSCallValue, args, argc, argv); +} + +void GraphBuilder::generate_CallWithReceiver(int name, int thisObject, int argc, int argv, + int /*traceSlot*/) +{ + VarArgNodes args; + args.append(env()->slot(name)); + args.append(env()->slot(thisObject)); + finalizeCall(Meta::JSCallWithReceiver, args, argc, argv); +} + +void GraphBuilder::generate_CallProperty(int name, int base, int argc, int argv, int /*traceSlot*/) +{ + VarArgNodes args; + args.append(env()->slot(base)); + args.append(createConstant(name)); + finalizeCall(Meta::JSCallProperty, args, argc, argv); +} + +void GraphBuilder::generate_CallPropertyLookup(int lookupIndex, int base, int argc, int argv, + int /*traceSlot*/) +{ + VarArgNodes args; + args.append(env()->slot(base)); + args.append(createConstant(lookupIndex)); + finalizeCall(Meta::JSCallLookup, args, argc, argv); +} + +void GraphBuilder::generate_CallElement(int base, int index, int argc, int argv, int /*traceSlot*/) +{ + VarArgNodes args; + args.append(env()->slot(base)); + args.append(env()->slot(index)); + finalizeCall(Meta::JSCallElement, args, argc, argv); +} + +void GraphBuilder::generate_CallName(int name, int argc, int argv, int /*traceSlot*/) +{ + VarArgNodes args; + args.append(createConstant(name)); + finalizeCall(Meta::JSCallName, args, argc, argv); +} + +void GraphBuilder::generate_CallPossiblyDirectEval(int argc, int argv, int /*traceSlot*/) +{ + VarArgNodes args; + finalizeCall(Meta::JSCallPossiblyDirectEval, args, argc, argv); +} + +void GraphBuilder::generate_CallGlobalLookup(int index, int argc, int argv, int /*traceSlot*/) +{ + VarArgNodes args; + args.append(createConstant(index)); + finalizeCall(Meta::JSCallGlobalLookup, args, argc, argv); +} + +void GraphBuilder::generate_CallQmlContextPropertyLookup(int index, int argc, int argv, + int /*traceSlot*/) +{ + VarArgNodes args; + args.append(createConstant(index)); + finalizeCall(Meta::QMLCallQmlContextPropertyLookup, args, argc, argv); +} + +void GraphBuilder::generate_SetUnwindHandler(int offset) +{ + m_currentUnwindHandlerOffset = offset ? absoluteOffset(offset) : 0; + env()->setUnwindHandlerOffset(m_currentUnwindHandlerOffset); +} + +void GraphBuilder::generate_UnwindDispatch() +{ + auto e = createNode(opBuilder()->get<Meta::HasException>(), graph()->engineNode()); + createNode(opBuilder()->get<Meta::Branch>(), e); + { + InterpreterSubEnvironment subEnvironment(this); + createIfTrue(); + startUnwinding(); + } + + createIfFalse(); + + const auto unwindHandlerOffset = env()->unwindHandlerOffset(); + const auto fallthroughSuccessor = nextInstructionOffset(); + auto nContinuations = m_func->unwindLabelOffsets().size() + 1; + if (unwindHandlerOffset) + ++nContinuations; + Q_ASSERT(nContinuations <= std::numeric_limits<unsigned>::max()); + createNode(opBuilder()->getUnwindDispatch(unsigned(nContinuations), unwindHandlerOffset, + fallthroughSuccessor)); + + { + InterpreterSubEnvironment fallthroughEnv(this); + mergeIntoSuccessor(fallthroughSuccessor); + } + + if (unwindHandlerOffset) { + InterpreterSubEnvironment unwindHandlerEnv(this); + createHandleUnwind(unwindHandlerOffset); + mergeIntoSuccessor(unwindHandlerOffset); + } + + for (int unwindLabelOffset : m_func->unwindLabelOffsets()) { + if (unwindLabelOffset <= currentInstructionOffset()) + continue; + InterpreterSubEnvironment unwindLabelEnv(this); + createHandleUnwind(unwindLabelOffset); + mergeIntoSuccessor(unwindLabelOffset); + } + + setEnv(nullptr); +} + +void GraphBuilder::generate_UnwindToLabel(int level, int offset) +{ + //### For de-optimization, the relative offset probably also needs to be stored + int unwinder = absoluteOffset(offset); + createNode(opBuilder()->get<Meta::UnwindToLabel>(), + createConstant(level), + createConstant(unwinder)); + m_func->addUnwindLabelOffset(unwinder); + startUnwinding(); +} + +void GraphBuilder::generate_DeadTemporalZoneCheck(int name) +{ + Node *check = createNode(opBuilder()->get<Meta::IsEmpty>(), env()->accumulator()); + createNode(opBuilder()->get<Meta::Branch>(), check); + + { //### it's probably better to handle this by de-optimizing + InterpreterSubEnvironment subEnvironment(this); + createIfTrue(); + createNode(opBuilder()->get<Meta::ThrowReferenceError>(), + createConstant(name)); + startUnwinding(); + } + + createIfFalse(); +} + +void GraphBuilder::generate_ThrowException() +{ + createNode(opBuilder()->get<Meta::Throw>(), env()->accumulator()); + startUnwinding(); +} + +void GraphBuilder::generate_GetException() +{ + bindAcc(createNode(opBuilder()->get<Meta::GetException>())); +} + +void GraphBuilder::generate_SetException() +{ + createNode(opBuilder()->get<Meta::SetException>(), + env()->accumulator()); +} + +void GraphBuilder::generate_CreateCallContext() +{ + createNode(opBuilder()->get<Meta::JSCreateCallContext>()); +} + +void GraphBuilder::generate_PushCatchContext(int index, int name) +{ + createNode(opBuilder()->get<Meta::JSCreateCatchContext>(), + createConstant(index), + createConstant(name)); +} + +void GraphBuilder::generate_PushWithContext() +{ + bindAcc(createNode(opBuilder()->get<Meta::JSCreateWithContext>(), + env()->accumulator())); +} + +void GraphBuilder::generate_PushBlockContext(int index) +{ + createNode(opBuilder()->get<Meta::JSCreateBlockContext>(), + createConstant(index)); +} + +void GraphBuilder::generate_CloneBlockContext() +{ + createNode(opBuilder()->get<Meta::JSCloneBlockContext>()); +} + +void GraphBuilder::generate_PushScriptContext(int index) +{ + createNode(opBuilder()->get<Meta::JSCreateScriptContext>(), createConstant(index)); +} + +void GraphBuilder::generate_PopScriptContext() +{ + createNode(opBuilder()->get<Meta::JSPopScriptContext>()); +} + +void GraphBuilder::generate_PopContext() +{ + createNode(opBuilder()->get<Meta::PopContext>()); +} + +void GraphBuilder::generate_GetIterator(int iterator) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSGetIterator>(), + env()->accumulator(), + createConstant(iterator))); +} + +void GraphBuilder::generate_IteratorNextAndFriends_TrailingStuff(Node *iterationNode, + int resultSlot) +{ + // See generate_IteratorNext for why this method exists. + + // check that no-one messed around with the operation and made it throwing + Q_ASSERT(iterationNode->operation()->controlOutputCount() == 1); + + // check that it's in the effect chain, because HasException relies on that + Q_ASSERT(iterationNode->operation()->effectOutputCount() == 1); + + env()->bindNodeToSlot(createNode(opBuilder()->get<Meta::SelectOutput>(), + iterationNode, + createConstant(1), + graph()->undefinedNode()), + resultSlot); + // Note: the following will NOT set the accumulator, because it contains the return value of + // the runtime call! + Node *ehCheck = createNode(opBuilder()->get<Meta::HasException>(), graph()->engineNode()); + createNode(opBuilder()->get<Meta::Branch>(), ehCheck); + + { // EH path: + InterpreterSubEnvironment subEnvironment(this); + createIfTrue(); + if (auto ehOffset = env()->unwindHandlerOffset()) { + // Ok, there is an exception handler, so go there: + mergeIntoSuccessor(ehOffset); + } else { + // No Exception Handler, so keep the exception set in the engine, and leave the function + // a.s.a.p.: + bindAcc(graph()->undefinedNode()); + generate_Ret(); + } + } + + // Normal control flow: + createIfFalse(); +} + +void GraphBuilder::generate_IteratorNext(int value, int done) +{ + // The way we model exceptions in the graph is that a runtime function will either succeed and + // return a value, or it fails and throws an exception. If it throws, the return value is not + // used because the method did not complete normally, and therefore it might be tainted. + // + // This is a problem for (and only for) IteratorNext and IteratorNextForYieldStart. + // + // What would happen in the normal case, is that the return value (done) is not used/assigned + // when IteratorNext throws, because the exception handling path is chosen. However, the + // interpreter *does* assign it, and will only check for an exception *after* that assignment. + // + // So, in order to work around this odd-duck behavior, we mark the operation as NoThrow, + // override the runtime method and flag it to not throw, and insert extra exception check nodes + // after the SelectOutput that follows the IteratorNext(ForYieldStar). + // + // Also note that the IteratorNext and IteratorNextForYieldStar are the only operations that + // have an inout parameter, and thus require a SelectOutput node to retrieve this. + + Node *n = createNode(opBuilder()->get<Meta::JSIteratorNext>(), + env()->accumulator(), + graph()->undefinedNode()); + bindAcc(n); + env()->bindNodeToSlot(n, done); + generate_IteratorNextAndFriends_TrailingStuff(n, value); +} + +void GraphBuilder::generate_IteratorNextForYieldStar(int iterator, int object) +{ + // Please, PLEASE read the comment in generate_IteratorNext. + Node *n = createNode(opBuilder()->get<Meta::JSIteratorNextForYieldStar>(), + env()->accumulator(), + env()->slot(iterator), + graph()->undefinedNode()); + // Note: the following is a tiny bit different from what generate_IteratorNext does. + bindAcc(n); + generate_IteratorNextAndFriends_TrailingStuff(n, object); +} + +void GraphBuilder::generate_IteratorClose(int done) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSIteratorClose>(), + env()->accumulator(), + env()->slot(done))); +} + +void GraphBuilder::generate_DestructureRestElement() +{ + bindAcc(createNode(opBuilder()->get<Meta::JSDestructureRestElement>(), + env()->accumulator())); +} + +void GraphBuilder::generate_DeleteProperty(int base, int index) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSDeleteProperty>(), + env()->slot(base), + env()->slot(index))); +} + +void GraphBuilder::generate_DeleteName(int name) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSDeleteName>(), + createConstant(name))); +} + +void GraphBuilder::generate_TypeofName(int name) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSTypeofName>(), + createConstant(name))); +} + +void GraphBuilder::generate_TypeofValue() +{ + bindAcc(createNode(opBuilder()->get<Meta::JSTypeofValue>(), env()->accumulator())); +} + +void GraphBuilder::generate_DeclareVar(int varName, int isDeletable) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSDeclareVar>(), + createConstant(isDeletable), + createConstant(varName))); +} + +void GraphBuilder::generate_DefineArray(int argc, int argv) +{ + VarArgNodes args; + finalizeCall(Meta::JSDefineArray, args, argc, argv); +} + +void GraphBuilder::generate_DefineObjectLiteral(int internalClassId, int argc, int argv) +{ + VarArgNodes args; + args.append(createConstant(internalClassId)); + finalizeCall(Meta::JSDefineObjectLiteral, args, argc, argv); +} + +void GraphBuilder::generate_CreateClass(int classIndex, int heritage, int computedNames) +{ + int argc = 0; + int argv = computedNames; + + const QV4::CompiledData::Class *cls = function()->v4Function()->compilationUnit->unitData() + ->classAt(classIndex); + const CompiledData::Method *methods = cls->methodTable(); + for (uint i = 0; i < cls->nStaticMethods + cls->nMethods; ++i) { + if (methods[i].name == std::numeric_limits<unsigned>::max()) + ++argc; + } + + VarArgNodes args; + args.append(createConstant(classIndex)); + args.append(env()->slot(heritage)); + finalizeCall(Meta::JSCreateClass, args, argc, argv); +} + +void GraphBuilder::generate_CreateMappedArgumentsObject() +{ + bindAcc(createNode(opBuilder()->get<Meta::JSCreateMappedArgumentsObject>())); +} + +void GraphBuilder::generate_CreateUnmappedArgumentsObject() +{ + bindAcc(createNode(opBuilder()->get<Meta::JSCreateUnmappedArgumentsObject>())); +} + +void GraphBuilder::generate_CreateRestParameter(int argIndex) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSCreateRestParameter>(), + createConstant(argIndex))); +} + +void GraphBuilder::generate_ConvertThisToObject() +{ + Node* control = createNode(opBuilder()->get<Meta::JSThisToObject>(), + env()->slot(CallData::This)); + env()->bindNodeToSlot(control, CallData::This); +} + +void GraphBuilder::generate_LoadSuperConstructor() +{ + bindAcc(createNode(opBuilder()->get<Meta::JSLoadSuperConstructor>(), + env()->slot(CallData::Function))); +} + +void GraphBuilder::generate_ToObject() +{ + bindAcc(createNode(opBuilder()->get<Meta::ToObject>(), + env()->accumulator())); +} + +void GraphBuilder::generate_CallWithSpread(int func, int thisObject, int argc, int argv, + int /*traceSlot*/) +{ + VarArgNodes args; + args.append(env()->slot(func)); + args.append(env()->slot(thisObject)); + finalizeCall(Meta::JSCallWithSpread, args, argc, argv); +} + +void GraphBuilder::generate_TailCall(int func, int thisObject, int argc, int argv) +{ + VarArgNodes args; + args.append(env()->slot(func)); + args.append(env()->slot(thisObject)); + populate(args, argc, argv); + Node *n = createAndLinkNode(opBuilder()->getJSTailCall(uint16_t(args.size())), args.data(), + size_t(args.size())); + queueFunctionExit(n); +} + +void GraphBuilder::generate_Construct(int func, int argc, int argv) +{ + VarArgNodes args; + args.append(env()->slot(func)); + args.append(env()->accumulator()); + finalizeCall(Meta::JSConstruct, args, argc, argv); +} + +void GraphBuilder::generate_ConstructWithSpread(int func, int argc, int argv) +{ + VarArgNodes args; + args.append(env()->slot(func)); + args.append(env()->accumulator()); + finalizeCall(Meta::JSConstructWithSpread, args, argc, argv); +} + +void GraphBuilder::generate_Jump(int offset) +{ + auto jumpTarget = absoluteOffset(offset); + mergeIntoSuccessor(jumpTarget); +} + +void GraphBuilder::generate_JumpTrue(int /*traceSlot*/, int offset) +{ + createNode(opBuilder()->get<Meta::Branch>(), createToBoolean(env()->accumulator())); + + { + InterpreterSubEnvironment subEnvironment(this); + auto jumpTarget = absoluteOffset(offset); + createIfTrue(); + mergeIntoSuccessor(jumpTarget); + } + + createIfFalse(); +} + +void GraphBuilder::generate_JumpFalse(int traceSlot, int offset) +{ + generate_JumpFalse(env()->accumulator(), traceSlot, offset); +} + +void GraphBuilder::generate_JumpFalse(Node *condition, int /*traceSlot*/, int offset) +{ + createNode(opBuilder()->get<Meta::Branch>(), createToBoolean(condition)); + + { + InterpreterSubEnvironment subEnvironment(this); + auto jumpTarget = absoluteOffset(offset); + createIfFalse(); + mergeIntoSuccessor(jumpTarget); + } + + createIfTrue(); +} + +void GraphBuilder::generate_JumpNoException(int offset) +{ + auto e = createNode(opBuilder()->get<Meta::HasException>(), graph()->engineNode()); + createNode(opBuilder()->get<Meta::Branch>(), e); + + { + InterpreterSubEnvironment subEnvironment(this); + auto jumpTarget = absoluteOffset(offset); + createIfFalse(); + mergeIntoSuccessor(jumpTarget); + } + + createIfTrue(); +} + +void GraphBuilder::generate_JumpNotUndefined(int offset) +{ + Node *condition = createNode(opBuilder()->get<Meta::JSStrictEqual>(), + env()->accumulator(), + graph()->undefinedNode()); + generate_JumpFalse(condition, NoTraceSlot, offset); +} + +void GraphBuilder::generate_CmpEqNull() +{ + bindAcc(createNode(opBuilder()->get<Meta::JSEqual>(), + env()->accumulator(), + graph()->nullNode())); +} + +void GraphBuilder::generate_CmpNeNull() +{ + generate_CmpEqNull(); + bindAcc(createNode(opBuilder()->get<Meta::BooleanNot>(), + env()->accumulator())); +} + +void GraphBuilder::generate_CmpEqInt(int lhs) +{ + auto left = createConstant(lhs); + Node* control = createNode(opBuilder()->get<Meta::JSEqual>(), + left, + env()->accumulator()); + bindAcc(control); +} + +void GraphBuilder::generate_CmpNeInt(int lhs) +{ + generate_CmpEqInt(lhs); + bindAcc(createNode(opBuilder()->get<Meta::BooleanNot>(), + env()->accumulator())); +} + +void GraphBuilder::generate_CmpEq(int lhs) +{ + Node* control = createNode(opBuilder()->get<Meta::JSEqual>(), + env()->slot(lhs), + env()->accumulator()); + bindAcc(control); +} + +void GraphBuilder::generate_CmpNe(int lhs) +{ + generate_CmpEq(lhs); + bindAcc(createNode(opBuilder()->get<Meta::BooleanNot>(), + env()->accumulator())); +} + +void GraphBuilder::generate_CmpGt(int lhs) +{ + Node* control = createNode(opBuilder()->get<Meta::JSGreaterThan>(), + env()->slot(lhs), + env()->accumulator()); + bindAcc(control); +} + +void GraphBuilder::generate_CmpGe(int lhs) +{ + Node* control = createNode(opBuilder()->get<Meta::JSGreaterEqual>(), + env()->slot(lhs), + env()->accumulator()); + bindAcc(control); +} + +void GraphBuilder::generate_CmpLt(int lhs) +{ + Node* control = createNode(opBuilder()->get<Meta::JSLessThan>(), + env()->slot(lhs), + env()->accumulator()); + bindAcc(control); +} + +void GraphBuilder::generate_CmpLe(int lhs) +{ + Node* control = createNode(opBuilder()->get<Meta::JSLessEqual>(), + env()->slot(lhs), + env()->accumulator()); + bindAcc(control); +} + +void GraphBuilder::generate_CmpStrictEqual(int lhs) +{ + Node* control = createNode(opBuilder()->get<Meta::JSStrictEqual>(), + env()->slot(lhs), + env()->accumulator()); + bindAcc(control); +} + +void GraphBuilder::generate_CmpStrictNotEqual(int lhs) +{ + generate_CmpStrictEqual(lhs); + bindAcc(createNode(opBuilder()->get<Meta::BooleanNot>(), + env()->accumulator())); +} + +void GraphBuilder::generate_CmpIn(int lhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSIn>(), env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_CmpInstanceOf(int lhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSInstanceOf>(), env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_UNot() +{ + bindAcc(createNode(opBuilder()->get<Meta::BooleanNot>(), + createToBoolean(env()->accumulator()))); +} + +void GraphBuilder::generate_UPlus(int /*traceSlot*/) +{ + Node* control = createNode(opBuilder()->get<Meta::JSToNumber>(), + env()->accumulator()); + bindAcc(control); +} + +void GraphBuilder::generate_UMinus(int /*traceSlot*/) +{ + Node* control = createNode(opBuilder()->get<Meta::JSNegate>(), + env()->accumulator()); + bindAcc(control); +} + +void GraphBuilder::generate_UCompl() +{ + bindAcc(createNode(opBuilder()->get<Meta::JSBitXor>(), + env()->accumulator(), + createConstant(-1))); +} + +void GraphBuilder::generate_Increment(int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSAdd>(), + env()->accumulator(), + createConstant(1))); +} + + +void GraphBuilder::generate_Decrement(int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSSubtract>(), + env()->accumulator(), + createConstant(1))); +} + +void GraphBuilder::generate_Add(int lhs, int /*traceSlot*/) +{ + Node* control = createNode(opBuilder()->get<Meta::JSAdd>(), + env()->slot(lhs), + env()->accumulator()); + bindAcc(control); +} + +void GraphBuilder::generate_BitAnd(int lhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSBitAnd>(), + env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_BitOr(int lhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSBitOr>(), + env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_BitXor(int lhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSBitXor>(), + env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_UShr(int lhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSUnsignedShiftRight>(), + env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_Shr(int lhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSShiftRight>(), + env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_Shl(int lhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSShiftLeft>(), + env()->slot(lhs), + env()->accumulator())); +} + + +void GraphBuilder::generate_BitAndConst(int rhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSBitAnd>(), + env()->accumulator(), + createConstant(rhs))); +} + +void GraphBuilder::generate_BitOrConst(int rhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSBitOr>(), + env()->accumulator(), + createConstant(rhs))); +} + +void GraphBuilder::generate_BitXorConst(int rhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSBitXor>(), + env()->accumulator(), + createConstant(rhs))); +} + +void GraphBuilder::generate_UShrConst(int rhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSUnsignedShiftRight>(), + env()->accumulator(), + createConstant(rhs))); +} + +void GraphBuilder::generate_ShrConst(int rhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSShiftRight>(), + env()->accumulator(), + createConstant(rhs))); +} + +void GraphBuilder::generate_ShlConst(int rhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSShiftLeft>(), + env()->accumulator(), + createConstant(rhs))); +} + +void GraphBuilder::generate_Exp(int lhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSExponentiate>(), + env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_Mul(int lhs, int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSMultiply>(), + env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_Div(int lhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSDivide>(), + env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_Mod(int lhs, int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSModulo>(), + env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_Sub(int lhs, int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSSubtract>(), + env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_InitializeBlockDeadTemporalZone(int firstReg, int count) +{ + for (int reg = firstReg; reg < firstReg + count; ++reg) + env()->bindNodeToSlot(graph()->emptyNode(), reg); +} + +void GraphBuilder::generate_ThrowOnNullOrUndefined() +{ + createNode(opBuilder()->get<Meta::JSThrowOnNullOrUndefined>(), + env()->accumulator()); +} + +void GraphBuilder::generate_GetTemplateObject(int index) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSGetTemplateObject>(), + createConstant(index))); +} + +GraphBuilder::Verdict GraphBuilder::startInstruction(Moth::Instr::Type /*instr*/) +{ + // This handles a couple of cases on how flow control can end up at this instruction. + + const auto off = currentInstructionOffset(); + if (auto newEnv = m_envForOffset[off]) { + // Ok, there was a jump from before to this point (which registered an environment), so we + // have two options: + if (env() != newEnv && env() != nullptr) { + // There is a current environment different from the environment active when we took the + // jump. This happens with e.g. an if-then-else: + // + // acc = condition + // JumpFalse else-block + // ... then block + // Jump end-if + // else-block: + // ... else block + // end-if: + // .. some instruction <--- we're here + // + // in that case we merge the after-else environment into the after-then environment: + newEnv->merge(env()); + } else { + // There is not a current environment. This can happen with e.g. a loop: + // loop-start: + // acc = condition + // JumpFalse loop-end + // ... loop body + // Jump loop-start + // loop-end: + // .... some instruction <--- we're here + // + // The last jump of the loop will clear the environment, so at this point we only have + // the environment registered by the JumpFalse. This is the asy case: no merges, just + // take the registered environment unchanged. + } + + // Leave the merged environment as-is, and continue with a copy. We cannot change the + // registered environment in case this point also happens to be a loop start. + setEnv(newEnv->copy()); + } + + if (env() == nullptr) { + // Ok, there is no environment, meaning nobody jumped to this instruction, and the previous + // instruction doesn't let control flow end up here. So, this is dead code. + // This can happen for JS like: + // + // if (condition) { + // return something + // } else { + // return somethingElse + // } + // someCode <--- we're here + return SkipInstruction; + } + + const LabelInfo *info = isLoopStart(off); + if (info && env()) { + // Ok, this instruction is the start of a loop, meaning there will be a jump backwards to + // this point. Make sure there is a Region node with Phi nodes here. + handleLoopStart(*info); + } + + return ProcessInstruction; +} + +void GraphBuilder::endInstruction(Moth::Instr::Type /*instr*/) {} + +} // IR namespace +} // QV4 namespace +QT_END_NAMESPACE diff --git a/src/qml/jit/qv4graphbuilder_p.h b/src/qml/jit/qv4graphbuilder_p.h new file mode 100644 index 0000000000..450d8640b7 --- /dev/null +++ b/src/qml/jit/qv4graphbuilder_p.h @@ -0,0 +1,298 @@ +/**************************************************************************** +** +** 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 QV4GRAPHBUILDER_P_H +#define QV4GRAPHBUILDER_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 <private/qv4global_p.h> +#include <private/qv4bytecodehandler_p.h> +#include <private/qv4ir_p.h> +#include "qv4graph_p.h" + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace IR { + +// The graph builder walks the byte-code, and produces a graph. The graph is a digraph, where the +// nodes have operations, and the edges are dependencies (or inputs). +class GraphBuilder: protected Moth::ByteCodeHandler +{ + Q_DISABLE_COPY_MOVE(GraphBuilder) + + enum { NoTraceSlot = -1 }; + + struct LabelInfo { //### extend this to also capture the amount of slots that are live + LabelInfo() = default; + LabelInfo(unsigned label) : labelOffset(label) {} + unsigned labelOffset = 0; + }; + +public: + static void buildGraph(IR::Function *function); + + class InterpreterEnvironment; + + void setEnv(InterpreterEnvironment *newEnv) + { m_currentEnv = newEnv; } + + InterpreterEnvironment *env() const + { return m_currentEnv; } + +private: + GraphBuilder(IR::Function *function); + ~GraphBuilder() override = default; + + void startGraph(); + void endGraph(); + + Node *bindAcc(Node *n); + Node *createAndLinkNode(Operation *op, Node *operands[], size_t opCount, bool incomplete = false); + Node *createNode(Operation *op, bool incomplete = false); + Node *createNode(Operation *op, Node *n1); + Node *createNode(Operation *op, Node *n1, Node *n2); + Node *createNode(Operation *op, Node *n1, Node *n2, Node *n3); + Node *createNode(Operation *op, Node *n1, Node *n2, Node *n3, Node *n4); + Node *createRegion(unsigned nControlInputs); + Node *createIfTrue(); + Node *createIfFalse(); + Node *createConstant(int v); + Node *createPhi(unsigned nInputs, Node *input, Node *control); + Node *createEffectPhi(unsigned nInputs, Node *input, Node *control); + Node *createHandleUnwind(int offset); + Node *mergeControl(Node *c1, Node *c2); + Node *mergeEffect(Node *e1, Node *e2, Node *control); + Node *mergeValue(Node *v1, Node *v2, Node *control); + + Node *createToBoolean(Node *input); + + using VarArgNodes = QVarLengthArray<Node *, 32>; + void populate(VarArgNodes &args, int argc, int argv); + + void queueFunctionExit(Node *exitNode); + + Function *function() const + { return m_func; } + + Graph *graph() + { return m_graph; } + + Node *mergeIntoSuccessor(int offset); + + OperationBuilder *opBuilder() const + { return m_graph->opBuilder(); } + + int absoluteOffset(int offset) const + { return offset + nextInstructionOffset(); } + + const LabelInfo *labelInfoAt(unsigned offset) const; + const LabelInfo *isLoopStart(unsigned offset) const; + void handleLoopStart(const LabelInfo &labelInfo); + void startUnwinding(); + +protected: // ByteCodeHandler + void generate_Ret() override; + void generate_Debug() override; + void generate_LoadConst(int index) override; + void generate_LoadZero() override; + void generate_LoadTrue() override; + void generate_LoadFalse() override; + void generate_LoadNull() override; + void generate_LoadUndefined() override; + void generate_LoadInt(int value) override; + void generate_MoveConst(int constIndex, int destTemp) override; + void generate_LoadReg(int reg) override; + void generate_StoreReg(int reg) override; + void generate_MoveReg(int srcReg, int destReg) override; + void generate_LoadImport(int index) override; + void generate_LoadLocal(int index, int traceSlot) override; + void generate_StoreLocal(int index) override; + void generate_LoadScopedLocal(int scope, int index, int traceSlot) override; + void generate_StoreScopedLocal(int scope, int index) override; + void generate_LoadRuntimeString(int stringId) override; + void generate_MoveRegExp(int regExpId, int destReg) override; + void generate_LoadClosure(int value) override; + void generate_LoadName(int name, int traceSlot) override; + void generate_LoadGlobalLookup(int index, int traceSlot) override; + void generate_StoreNameSloppy(int name) override; + void generate_StoreNameStrict(int name) override; + void generate_LoadElement(int base, int traceSlot) override; + void generate_StoreElement(int base, int index, int traceSlot) override; + void generate_LoadProperty(int name, int traceSlot) override; + void generate_GetLookup(int index, int traceSlot) override; + void generate_StoreProperty(int name, int base) override; + void generate_SetLookup(int index, int base) override; + void generate_LoadSuperProperty(int property) override; + void generate_StoreSuperProperty(int property) override; + void generate_LoadQmlContextPropertyLookup(int property, int traceSlot) override; + void generate_Yield() override; + void generate_YieldStar() override; + void generate_Resume(int offset) override; + void finalizeCall(Operation::Kind kind, VarArgNodes &args, int argc, int argv); + void generate_CallValue(int name, int argc, int argv, int traceSlot) override; + void generate_CallWithReceiver(int name, int thisObject, int argc, int argv, + int traceSlot) override; + void generate_CallProperty(int name, int base, int argc, int argv, int traceSlot) override; + void generate_CallPropertyLookup(int lookupIndex, int base, int argc, int argv, + int traceSlot) override; + void generate_CallElement(int base, int index, int argc, int argv, int traceSlot) override; + void generate_CallName(int name, int argc, int argv, int traceSlot) override; + void generate_CallPossiblyDirectEval(int argc, int argv, int traceSlot) override; + void generate_CallGlobalLookup(int index, int argc, int argv, int traceSlot) override; + void generate_CallQmlContextPropertyLookup(int index, int argc, int argv, int traceSlot) override; + void generate_SetUnwindHandler(int offset) override; + void generate_UnwindDispatch() override; + void generate_UnwindToLabel(int level, int offset) override; + void generate_DeadTemporalZoneCheck(int name) override; + void generate_ThrowException() override; + void generate_GetException() override; + void generate_SetException() override; + void generate_CreateCallContext() override; + void generate_PushCatchContext(int index, int name) override; + void generate_PushWithContext() override; + void generate_PushBlockContext(int index) override; + void generate_CloneBlockContext() override; + void generate_PushScriptContext(int index) override; + void generate_PopScriptContext() override; + void generate_PopContext() override; + void generate_GetIterator(int iterator) override; + void generate_IteratorNextAndFriends_TrailingStuff(Node *iterationNode, int resultSlot); + void generate_IteratorNext(int value, int done) override; + void generate_IteratorNextForYieldStar(int iterator, int object) override; + void generate_IteratorClose(int done) override; + void generate_DestructureRestElement() override; + void generate_DeleteProperty(int base, int index) override; + void generate_DeleteName(int name) override; + void generate_TypeofName(int name) override; + void generate_TypeofValue() override; + void generate_DeclareVar(int varName, int isDeletable) override; + void generate_DefineArray(int argc, int argv) override; + void generate_DefineObjectLiteral(int internalClassId, int argc, int argv) override; + void generate_CreateClass(int classIndex, int heritage, int computedNames) override; + void generate_CreateMappedArgumentsObject() override; + void generate_CreateUnmappedArgumentsObject() override; + void generate_CreateRestParameter(int argIndex) override; + void generate_ConvertThisToObject() override; + void generate_LoadSuperConstructor() override; + void generate_ToObject() override; + void generate_CallWithSpread(int func, int thisObject, int argc, int argv, + int traceSlot) override; + void generate_TailCall(int func, int thisObject, int argc, int argv) override; + void generate_Construct(int func, int argc, int argv) override; + void generate_ConstructWithSpread(int func, int argc, int argv) override; + void generate_Jump(int offset) override; + void generate_JumpTrue(int traceSlot, int offset) override; + void generate_JumpFalse(int traceSlot, int offset) override; + void generate_JumpFalse(Node *condition, int traceSlot, int offset); + void generate_JumpNoException(int offset) override; + void generate_JumpNotUndefined(int offset) override; + void generate_CmpEqNull() override; + void generate_CmpNeNull() override; + void generate_CmpEqInt(int lhs) override; + void generate_CmpNeInt(int lhs) override; + void generate_CmpEq(int lhs) override; + void generate_CmpNe(int lhs) override; + void generate_CmpGt(int lhs) override; + void generate_CmpGe(int lhs) override; + void generate_CmpLt(int lhs) override; + void generate_CmpLe(int lhs) override; + void generate_CmpStrictEqual(int lhs) override; + void generate_CmpStrictNotEqual(int lhs) override; + void generate_CmpIn(int lhs) override; + void generate_CmpInstanceOf(int lhs) override; + void generate_UNot() override; + void generate_UPlus(int traceSlot) override; + void generate_UMinus(int traceSlot) override; + void generate_UCompl() override; + void generate_Increment(int traceSlot) override; + void generate_Decrement(int traceSlot) override; + void generate_Add(int lhs, int traceSlot) override; + void generate_BitAnd(int lhs) override; + void generate_BitOr(int lhs) override; + void generate_BitXor(int lhs) override; + void generate_UShr(int lhs) override; + void generate_Shr(int lhs) override; + void generate_Shl(int lhs) override; + void generate_BitAndConst(int rhs) override; + void generate_BitOrConst(int rhs) override; + void generate_BitXorConst(int rhs) override; + void generate_UShrConst(int rhs) override; + void generate_ShrConst(int rhs) override; + void generate_ShlConst(int rhs) override; + void generate_Exp(int lhs) override; + void generate_Mul(int lhs, int traceSlot) override; + void generate_Div(int lhs) override; + void generate_Mod(int lhs, int traceSlot) override; + void generate_Sub(int lhs, int traceSlot) override; + void generate_InitializeBlockDeadTemporalZone(int firstReg, int count) override; + void generate_ThrowOnNullOrUndefined() override; + void generate_GetTemplateObject(int index) override; + + Verdict startInstruction(Moth::Instr::Type instr) override; + void endInstruction(Moth::Instr::Type instr) override; + +private: + IR::Function *m_func; + Graph *m_graph; + InterpreterEnvironment *m_currentEnv; + std::vector<Node *> m_exitControls; + QHash<int, InterpreterEnvironment *> m_envForOffset; + std::vector<LabelInfo> m_labelInfos; + int m_currentUnwindHandlerOffset = 0; +}; + +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4GRAPHBUILDER_P_H diff --git a/src/qml/jit/qv4ir.cpp b/src/qml/jit/qv4ir.cpp new file mode 100644 index 0000000000..0b82330394 --- /dev/null +++ b/src/qml/jit/qv4ir.cpp @@ -0,0 +1,382 @@ +/**************************************************************************** +** +** 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 <private/qqmlglobal_p.h> +#include "qv4ir_p.h" +#include "qv4node_p.h" +#include "qv4function_p.h" +#include <qv4graph_p.h> +#include "qv4stackframe_p.h" +#include "qv4operation_p.h" +#include "qv4util_p.h" + +#include <QtCore/qloggingcategory.h> +#include <QtCore/qjsonobject.h> +#include <QtCore/qjsonarray.h> +#include <QtCore/qfile.h> + +QT_BEGIN_NAMESPACE +namespace QV4 { +namespace IR { + +Q_LOGGING_CATEGORY(lcJsonIR, "qt.v4.ir.json"); +Q_LOGGING_CATEGORY(lcDotIR, "qt.v4.ir.dot"); +Q_LOGGING_CATEGORY(lcVerify, "qt.v4.ir.verify"); + +Function::Function(QV4::Function *qv4Function) + : qv4Function(qv4Function) + , m_graph(Graph::create(this)) + , m_dumper(nullptr) + , m_nodeInfo(128, nullptr) +{ +} + +Function::~Function() +{ + delete m_dumper; +} + +QString Function::name() const +{ + QString name; + if (auto n = v4Function()->name()) + name = n->toQString(); + if (name.isEmpty()) + name.sprintf("%p", static_cast<void *>(v4Function())); + auto loc = v4Function()->sourceLocation(); + return name + QStringLiteral(" (%1:%2:%3)").arg(loc.sourceFile, QString::number(loc.line), + QString::number(loc.column)); +} + +void Function::dump(const QString &description) const +{ + Dumper::dump(this, description); +} + +void Function::dump() const +{ + dump(QStringLiteral("Debug:")); +} + +Dumper *Function::dumper() const +{ + if (!m_dumper) + m_dumper = new Dumper(this); + return m_dumper; +} + +Function::StringId Function::addString(const QString &s) +{ + m_stringPool.push_back(s); + return m_stringPool.size() - 1; +} + +NodeInfo *Function::nodeInfo(Node *n, bool createIfNecessary) const +{ + if (n->id() >= m_nodeInfo.size()) + m_nodeInfo.resize(n->id() * 2, nullptr); + + NodeInfo *&info = m_nodeInfo[n->id()]; + if (info == nullptr && createIfNecessary) { + info = m_pool.New<NodeInfo>(); + info->setType(n->operation()->type()); + } + return info; +} + +void Function::copyBytecodeOffsets(Node *from, Node *to) +{ + auto toInfo = nodeInfo(to); + if (auto fromInfo = nodeInfo(from)) { + toInfo->setBytecodeOffsets(fromInfo->currentInstructionOffset(), + fromInfo->nextInstructionOffset()); + } +} + +Dumper::Dumper(const Function *f) +{ + if (!f) + return; +} + +void Dumper::dump(const Function *f, const QString &description) +{ + if (false && lcJsonIR().isDebugEnabled()) { + Dumper *dumper = f->dumper(); + + qCDebug(lcJsonIR).noquote().nospace() << description + QLatin1String(":\n"); + for (const auto &line : dumper->dump(f).split('\n')) + qCDebug(lcJsonIR).noquote().nospace() << line; + } + + if (lcDotIR().isDebugEnabled()) + dot(f, description); +} + +QByteArray Dumper::dump(const Function *f) +{ + QJsonObject fo; + + { + QString name; + if (auto n = f->v4Function()->name()) + name = n->toQString(); + fo[QLatin1String("_searchKey")] = QStringLiteral("function %1").arg(name); + if (name.isEmpty()) + name.sprintf("%p", static_cast<void *>(f->v4Function())); + fo[QLatin1String("name")] = name; + } + + auto loc = f->v4Function()->sourceLocation(); + fo[QLatin1String("source")] = loc.sourceFile; + fo[QLatin1String("line")] = loc.line; + fo[QLatin1String("column")] = loc.column; + + { + QJsonArray gn; + QJsonArray ge; + NodeCollector nodes(f->graph(), /*collectUses =*/ true); + nodes.sortById(); + for (Node *n : nodes.reachable()) { + gn.append(dump(n, f)); + int inputIndex = 0; + for (Node *input : n->inputs()) { + QJsonObject edge; + edge[QLatin1String("from")] = int(input->id()); + edge[QLatin1String("to")] = int(n->id()); + edge[QLatin1String("index")] = inputIndex; + if (inputIndex < n->operation()->valueInputCount()) { + edge[QLatin1String("type")] = QLatin1String("value"); + } else if (inputIndex < n->operation()->valueInputCount() + + n->operation()->effectInputCount()) { + edge[QLatin1String("type")] = QLatin1String("effect"); + } else { + edge[QLatin1String("type")] = QLatin1String("control"); + } + Q_ASSERT(inputIndex < n->operation()->valueInputCount() + + n->operation()->effectInputCount() + + n->operation()->controlInputCount()); + ge.append(edge); + ++inputIndex; + } + } + QJsonObject g; + g[QLatin1String("nodes")] = gn; + g[QLatin1String("edges")] = ge; + fo[QLatin1String("graph")] = g; + } + + m_doc.setObject(fo); + return m_doc.toJson(QJsonDocument::Indented); +} + +QJsonValue toJSonValue(QV4::Value v) +{ + switch (v.type()) { + case QV4::Value::Undefined_Type: return QJsonValue(QJsonValue::Undefined); + case QV4::Value::Null_Type: return QJsonValue(QJsonValue::Null); + case QV4::Value::Boolean_Type: return QJsonValue(v.booleanValue()); + case QV4::Value::Integer_Type: return QJsonValue(v.int_32()); + case QV4::Value::Managed_Type: + if (String *s = v.stringValue()) + return QJsonValue(s->toQString()); + else + return QJsonValue(QLatin1String("<managed>")); + default: return QJsonValue(v.doubleValue()); + } +} + +QJsonValue Dumper::dump(const Node * const node, const Function *f) +{ + QJsonObject n; + n[QLatin1String("id")] = int(node->id()); + n[QLatin1String("kind")] = node->operation()->debugString(); + switch (node->operation()->kind()) { + case Meta::Parameter: { + auto info = ParameterPayload::get(*node->operation()); + n[QLatin1String("name")] = f->string(info->stringId()); + n[QLatin1String("index")] = int(info->parameterIndex()); + break; + } + case Meta::Constant: { + auto info = ConstantPayload::get(*node->operation()); + n[QLatin1String("value")] = toJSonValue(info->value()); + break; + } + default: + break; + } + return n; +} + +void Dumper::dot(const Function *f, const QString &description) +{ + static const bool skipFramestate = qEnvironmentVariableIsSet("QV4_JIT_DOT_SKIP_FRAMESTATE"); + + auto node = [](Node *n) { + return QStringLiteral("n%1[label=\"%1: %2%3\"];\n").arg(QString::number(n->id()), + n->operation()->debugString(), + n->isDead() ? QStringLiteral(" (dead)") + : QString()); + }; + + Graph *g = f->graph(); + QString out; + out += QLatin1Char('\n'); + out += QStringLiteral("digraph{root=\"n%1\" label=\"%2\";" + "node[shape=rect];" + "edge[dir=back fontsize=10];\n") + .arg(g->startNode()->id()) + .arg(description); + out += node(g->startNode()); + const bool dumpUses = false; // set to true to see all nodes + NodeCollector nodes(g, dumpUses, skipFramestate); + for (Node *n : nodes.reachable()) { + if (n == g->startNode()) + continue; + + out += node(n); + + unsigned inputIndex = 0; + for (Node *input : n->inputs()) { + if (input == nullptr) + continue; + out += QStringLiteral("n%2->n%1[style=").arg(QString::number(n->id()), + QString::number(input->id())); + if (inputIndex < n->operation()->valueInputCount() || + inputIndex == n->operation()->indexOfFrameStateInput()) { + out += QStringLiteral("solid headlabel=\"%1\"").arg(inputIndex); + } else if (inputIndex < unsigned(n->operation()->valueInputCount() + + n->operation()->effectInputCount())) { + out += QStringLiteral("dotted headlabel=\"%1\"").arg(inputIndex); + } else { + out += QStringLiteral("dashed headlabel=\"%1\"").arg(inputIndex); + } + out += QStringLiteral("];\n"); + ++inputIndex; + } + } + out += QStringLiteral("}\n"); + qCDebug(lcDotIR).nospace().noquote() << out; + + QFile of(description + QStringLiteral(".dot")); + of.open(QIODevice::WriteOnly); + of.write(out.toUtf8()); + of.close(); +} + +void Function::verify() const +{ +#ifndef QT_NO_DEBUG + unsigned problemsFound = 0; + + auto verifyNodeAgainstOperation = [&problemsFound](const Node *n) { + const Operation *op = n->operation(); + if (op->totalInputCount() != n->inputCount()) { + ++problemsFound; + qCDebug(lcVerify()) << "Node" << n->id() << "has" << n->inputCount() + << "inputs, but it's operation" << op->debugString() + << "requires" << op->totalInputCount() << "inputs"; + } + + if (n->opcode() == Meta::Phi || n->opcode() == Meta::EffectPhi) { + if (n->controlInput()->opcode() != Meta::Region) { + ++problemsFound; + qCDebug(lcVerify()) << "Control input of phi node" << n->id() << "is not a region"; + } + if (n->controlInput()->inputCount() + 1 != n->inputCount()) { + ++problemsFound; + qCDebug(lcVerify()) << "Control input of phi node" << n->id() + << "has" << n->controlInput()->inputCount() + << "inputs while phi node has" << n->inputCount() + << "inputs"; + } + } + + //### todo: verify outputs: value outputs are allowed to be unused, but the effect and + // control outputs have to be linked up, except: + //### todo: verify if no use is a nullptr, except for operations that can throw, where the + // last one is allowed to be a nullptr when an unwind handler is missing. + }; + + NodeWorkList todo(graph()); + todo.enqueue(graph()->endNode()); + while (Node *n = todo.dequeueNextNodeForVisiting()) { + todo.enqueueAllInputs(n); + todo.enqueueAllUses(n); + + verifyNodeAgainstOperation(n); + } + //### TODO: + if (problemsFound != 0) { + dump(QStringLiteral("Problematic graph")); + qFatal("Found %u problems during graph verification!", problemsFound); + } +#endif // QT_NO_xDEBUG +} + +QString Type::debugString() const +{ + if (isNone()) + return QStringLiteral("none"); + if (isInvalid()) + return QStringLiteral("invalid"); + + QStringList s; + if (m_t & Bool) + s += QStringLiteral("boolean"); + if (m_t & Int32) + s += QStringLiteral("int32"); + if (m_t & Double) + s += QStringLiteral("double"); + if (m_t & Undefined) + s += QStringLiteral("undefined"); + if (m_t & Null) + s += QStringLiteral("null"); + if (m_t & Empty) + s += QStringLiteral("empty"); + if (m_t & RawPointer) + s += QStringLiteral("raw pointer"); + + return s.join(QLatin1String(" ")); +} + +} // IR namespace +} // QV4 namespace +QT_END_NAMESPACE diff --git a/src/qml/jit/qv4ir_p.h b/src/qml/jit/qv4ir_p.h new file mode 100644 index 0000000000..e21a80528d --- /dev/null +++ b/src/qml/jit/qv4ir_p.h @@ -0,0 +1,228 @@ +/**************************************************************************** +** +** 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 QV4IR_P_H +#define QV4IR_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 <private/qv4function_p.h> +#include <QtCore/qjsondocument.h> + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace IR { + +class Dumper; +class Graph; + +class Node; +class NodeInfo; + +class Function +{ + Q_DISABLE_COPY_MOVE(Function) +public: + Function(QV4::Function *qv4Function); + ~Function(); + + void verify() const; + + QV4::Function *v4Function() const + { return qv4Function; } + + QString name() const; + + QQmlJS::MemoryPool *pool() + { return &m_pool; } + + Graph *graph() const + { return m_graph; } + + void dump(const QString &description) const; + void dump() const; // for calling in the debugger + Dumper *dumper() const; + + using StringId = size_t; + StringId addString(const QString &s); + QString string(StringId id) const + { return m_stringPool[id]; } + + NodeInfo *nodeInfo(Node *n, bool createIfNecessary = true) const; + void copyBytecodeOffsets(Node *from, Node *to); + + void addUnwindLabelOffset(int absoluteOffset) + { m_unwindLabelOffsets.push_back(absoluteOffset); } + + const std::vector<int> &unwindLabelOffsets() const + { return m_unwindLabelOffsets; } + +private: + QV4::Function *qv4Function; + mutable QQmlJS::MemoryPool m_pool; + Graph *m_graph; + mutable Dumper *m_dumper; + std::vector<QString> m_stringPool; + mutable std::vector<NodeInfo *> m_nodeInfo; //### move the into the _pool + std::vector<int> m_unwindLabelOffsets; +}; + +class Dumper +{ + Q_DISABLE_COPY_MOVE(Dumper) + +public: + Dumper(const Function *f); + ~Dumper() = default; + + static void dump(const Function *f, const QString &description); + static void dot(const Function *f, const QString &description); + +private: + QByteArray dump(const Function *f); + QJsonValue dump(const Node *node, const Function *f); + +private: + QJsonDocument m_doc; +}; + +class Type +{ + // None is for nodes with no type (e.g. a Return) + // The others form a lattice: + // Any -> Object -> Invalid + // ^^^ -> Number -> Integral -> Int32 -> ^^^^^^^ + // ^^^ -> Number -> Integral -> UInt32 -> ^^^^^^^ + // ^^^ -> Number -> Integral -> Bool -> ^^^^^^^ + // ^^^ -> Number -> Double -> ^^^^^^^ + // ^^^ -> Undefined -> ^^^^^^^ + // ^^^ -> Null -> ^^^^^^^ + // ^^^ -> Empty -> ^^^^^^^ + enum InternalType: int16_t { + None = 0, + + Object = 1 << 0, + Bool = 1 << 1, + Int32 = 1 << 2, + UInt32 = 1 << 3, + Double = 1 << 4, + Undefined = 1 << 5, + Null = 1 << 6, + Empty = 1 << 7, + RawPointer = 1 << 8, + Invalid = -1, + + Integral = Int32 | UInt32 | Bool, + Number = Integral | Double, + Any = Object | Number | Undefined | Empty | Null, + }; + + Type(InternalType t) : m_t(t) {} + +public: + Type() = default; + + bool operator==(const Type &other) const + { return m_t == other.m_t; } + + static Type noneType() { return Type(None); } + static Type anyType() { return Type(Any); } + static Type undefinedType() { return Type(Undefined); } + static Type emptyType() { return Type(Empty); } + static Type booleanType() { return Type(Bool); } + static Type int32Type() { return Type(Int32); } + static Type doubleType() { return Type(Double); } + static Type numberType() { return Type(Number); } + static Type nullType() { return Type(Null); } + static Type objectType() { return Type(Object); } + static Type rawPointerType() { return Type(RawPointer); } + + bool isAny() const { return m_t == Any; } + bool isBoolean() const { return m_t == Bool; } + bool isInt32() const { return m_t == Int32; } + bool isInvalid() const { return m_t == Invalid; } + bool isNone() const { return m_t == None; } + bool isDouble() const { return m_t == Double; } + bool isUndefined() const { return m_t == Undefined; } + bool isNull() const { return m_t == Null; } + bool isEmpty() const { return m_t == Empty; } + bool isObject() const { return m_t == Object; } + bool isRawPointer() const { return m_t == RawPointer; } + bool isIntegral() const { return matches(Integral); } + bool isNumber() const { return matches(Number); } + + Type operator|(Type other) const + { return Type(InternalType(int16_t(m_t) | int16_t(other.m_t))); } + + Type &operator|=(Type other) + { + m_t = (InternalType(int16_t(m_t) | int16_t(other.m_t))); + return *this; + } + + QString debugString() const; + +private: + bool matches(InternalType it) const + { + return (m_t & ~it) == 0 && (m_t & it) != 0; + } + +private: + InternalType m_t = None; +}; + +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4IR_P_H diff --git a/src/qml/jit/qv4jithelpers.cpp b/src/qml/jit/qv4jithelpers.cpp deleted file mode 100644 index 674fd8c8c8..0000000000 --- a/src/qml/jit/qv4jithelpers.cpp +++ /dev/null @@ -1,174 +0,0 @@ -/**************************************************************************** -** -** 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 "qv4jithelpers_p.h" -#include "qv4engine_p.h" -#include "qv4function_p.h" -#include "qv4value_p.h" -#include "qv4object_p.h" -#include "qv4functionobject_p.h" -#include "qv4lookup_p.h" -#include <QtCore/private/qnumeric_p.h> - -#ifdef V4_ENABLE_JIT - -QT_BEGIN_NAMESPACE -namespace QV4 { -namespace JIT { -namespace Helpers { - -void convertThisToObject(ExecutionEngine *engine, Value *t) -{ - if (!t->isObject()) { - if (t->isNullOrUndefined()) { - *t = engine->globalObject->asReturnedValue(); - } else { - *t = t->toObject(engine)->asReturnedValue(); - } - } -} - -ReturnedValue loadGlobalLookup(Function *f, ExecutionEngine *engine, int index) -{ - Lookup *l = f->compilationUnit->runtimeLookups + 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()) - return obj.asReturnedValue(); - - return obj.toObject(engine)->asReturnedValue(); -} - -ReturnedValue exp(const Value &base, const Value &exp) -{ - double b = base.toNumber(); - double e = exp.toNumber(); - if (qt_is_inf(e) && (b == 1 || b == -1)) - return Encode(qt_snan()); - return Encode(pow(b,e)); -} - -ReturnedValue getLookup(Function *f, ExecutionEngine *engine, const Value &base, int index) -{ - Lookup *l = f->compilationUnit->runtimeLookups + index; - return l->getter(l, engine, base); -} - -void setLookupSloppy(Function *f, int index, Value &base, const Value &value) -{ - ExecutionEngine *engine = f->internalClass->engine; - QV4::Lookup *l = f->compilationUnit->runtimeLookups + index; - l->setter(l, engine, base, value); -} - -void setLookupStrict(Function *f, int index, Value &base, const Value &value) -{ - ExecutionEngine *engine = f->internalClass->engine; - QV4::Lookup *l = f->compilationUnit->runtimeLookups + index; - if (!l->setter(l, engine, base, value)) - engine->throwTypeError(); -} - - -void pushBlockContext(Value *stack, int index) -{ - ExecutionContext *c = static_cast<ExecutionContext *>(stack + CallData::Context); - stack[CallData::Context] = Runtime::method_createBlockContext(c, index); -} - -void cloneBlockContext(Value *contextSlot) -{ - *contextSlot = Runtime::method_cloneBlockContext(static_cast<QV4::ExecutionContext *>(contextSlot)); -} - -void pushScriptContext(Value *stack, ExecutionEngine *engine, int index) -{ - stack[CallData::Context] = Runtime::method_createScriptContext(engine, index); -} - -void popScriptContext(Value *stack, ExecutionEngine *engine) -{ - stack[CallData::Context] = Runtime::method_popScriptContext(engine); -} - -ReturnedValue deleteProperty(QV4::Function *function, const QV4::Value &base, const QV4::Value &index) -{ - auto engine = function->internalClass->engine; - if (!Runtime::method_deleteProperty(engine, base, index)) { - if (function->isStrict()) - engine->throwTypeError(); - return Encode(false); - } else { - return Encode(true); - } -} - -ReturnedValue deleteName(Function *function, int name) -{ - auto engine = function->internalClass->engine; - if (!Runtime::method_deleteName(engine, name)) { - if (function->isStrict()) - engine->throwTypeError(); - return Encode(false); - } else { - return Encode(true); - } -} - -void throwOnNullOrUndefined(ExecutionEngine *engine, const Value &v) -{ - if (v.isNullOrUndefined()) - engine->throwTypeError(); -} - -} // Helpers namespace -} // JIT namespace -} // QV4 namespace -QT_END_NAMESPACE - -#endif // V4_ENABLE_JIT 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 <QtCore/qloggingcategory.h> + +#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<MIBlock *> 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<MIBlock *> &backedges) +{ + blockInfos[loopHead->index()].isLoopHeader = true; + + std::vector<MIBlock *> 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("<none>"); + if (bi.isLoopHeader) { + s += QStringLiteral(", loop exits: "); + if (bi.loopExits.empty()) { + s += QLatin1String("<none>"); + } 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 <QHash> + +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<MIBlock *> 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<MIBlock *> loopExitsForLoop(MIBlock *loopHeader) const + { return blockInfos[loopHeader->index()].loopExits; } + +private: + void subLoop(MIBlock *loopHead, const std::vector<MIBlock *> &backedges); + void collectLoopExits(); + bool inLoopOrSubLoop(MIBlock *block, MIBlock *loopHeader) const; + + void dump() const; + +private: + const DominatorTree &dt; + std::vector<BlockInfo> blockInfos; +}; + +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4LOOPINFO_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 <QLoggingCategory> + +#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<Node *, 32> 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<Meta::Alloca>(), 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<Meta::VAAlloc>(), + graph()->createConstantIntNode(n->operation()->valueInputCount() - varArgsStart), + n->effectInput()); + QVarLengthArray<Node *, 32> vaSealIn; + vaSealIn.append(vaAlloc); + for (unsigned i = varArgsStart, ei = n->operation()->valueInputCount(); i != ei; ++i) { + vaSealIn.append(graph()->createNode(opBuilder()->get<Meta::VAStore>(), 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<Node *, 8> 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<Meta::Alloca>(), 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/qv4jithelpers_p.h b/src/qml/jit/qv4lowering_p.h index d9abfc071e..0b482bc9f0 100644 --- a/src/qml/jit/qv4jithelpers_p.h +++ b/src/qml/jit/qv4lowering_p.h @@ -37,8 +37,8 @@ ** ****************************************************************************/ -#ifndef TEMPLATE_H -#define TEMPLATE_H +#ifndef QV4LOWERING_P_H +#define QV4LOWERING_P_H // // W A R N I N G @@ -51,42 +51,57 @@ // We mean it. // +#include <private/qqmljsmemorypool_p.h> #include <private/qv4global_p.h> +#include <private/qv4ir_p.h> +#include <private/qv4util_p.h> +#include <private/qv4node_p.h> +#include <private/qv4graph_p.h> -//QT_REQUIRE_CONFIG(qml_jit); +QT_REQUIRE_CONFIG(qml_tracing); QT_BEGIN_NAMESPACE namespace QV4 { +namespace IR { -#ifdef V4_ENABLE_JIT +// 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. -namespace JIT { -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); -void setLookupStrict(Function *f, int index, Value &base, const Value &value); -void setLookupSloppy(Function *f, int index, Value &base, const Value &value); -void pushBlockContext(Value *stack, int index); -void cloneBlockContext(Value *contextSlot); -void pushScriptContext(Value *stack, ExecutionEngine *engine, int index); -void popScriptContext(Value *stack, ExecutionEngine *engine); -ReturnedValue deleteProperty(QV4::Function *function, const QV4::Value &base, const QV4::Value &index); -ReturnedValue deleteName(Function *function, int name); -void throwOnNullOrUndefined(ExecutionEngine *engine, const Value &v); +// 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) -} // Helpers namespace -} // JIT namespace +public: + GenericLowering(Function &f); -#endif // V4_ENABLE_JIT + void lower(); -} // QV4 namespace +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 // TEMPLATE_H +#endif // QV4LOWERING_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 <QtCore/qloggingcategory.h> +#include <private/qqmlglobal_p.h> + +#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("<<INVALID>>"); + 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("<<INVALID BLOCK>>"); + default: Q_UNREACHABLE(); + } +} + +MIInstr *MIInstr::create(QQmlJS::MemoryPool *pool, Node *irNode, unsigned nOperands) +{ + return pool->New<MIInstr>(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<void *>(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("<none>"); + } 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("<none>"); + } 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 <private/qv4global_p.h> +#include <private/qv4ir_p.h> +#include <private/qv4node_p.h> +#include <private/qv4operation_p.h> + +#include <llvm/ADT/iterator.h> +#include <llvm/ADT/iterator_range.h> +#include <llvm/ADT/ilist.h> +#include <llvm/ADT/ilist_node.h> + +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<MIOperand>; + +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 <typename NodeTy> struct MIInstrListParentType {}; +template <> struct MIInstrListParentType<MIInstr> { using type = MIBlock; }; + +template <typename NodeTy> class MIInstrList; + +template <typename MISubClass> +class MIInstrListTraits : public llvm::ilist_noalloc_traits<MISubClass> +{ +protected: + using ListTy = MIInstrList<MISubClass>; + using iterator = typename llvm::simple_ilist<MISubClass>::iterator; + using ItemParentClass = typename MIInstrListParentType<MISubClass>::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<MISubClass> *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 T> +class MIInstrList: public llvm::iplist_impl<llvm::simple_ilist<T>, MIInstrListTraits<T>> +{ +public: + MIInstrList(typename MIInstrListTraits<T>::ItemParentClass *owner) + { this->setListOwner(owner); } +}; + +class MIInstr final : public llvm::ilist_node_with_parent<MIInstr, MIBlock> +{ + 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<MIInstr>::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<Index>::max() }; + + using MIInstructionList = MIInstrList<MIInstr>; + + using InEdges = QVarLengthArray<MIBlock *, 4>; + using OutEdges = QVarLengthArray<MIBlock *, 2>; + +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<MIOperand> &arguments() const + { return m_arguments; } + + std::vector<MIOperand> &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<MIOperand> 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<MIBlock *> &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<MIBlock *> &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<MIBlock *> 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<MIInstr>::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<MIBlock::Index, 8> 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<MIBlock *> &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<MIBlock::Index, 8>::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<MIBlock *> &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.cpp b/src/qml/jit/qv4node.cpp new file mode 100644 index 0000000000..e059e9fef6 --- /dev/null +++ b/src/qml/jit/qv4node.cpp @@ -0,0 +1,215 @@ +/**************************************************************************** +** +** 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 "qv4node_p.h" +#include "qv4graph_p.h" + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE +namespace QV4 { +namespace IR { + +Node *Node::create(Node::MemoryPool *pool, Node::Id id, const Operation *op, size_t nInputs, + Node *const *inputs, bool inputsAreExtensible) +{ + size_t capacity = nInputs; + if (inputsAreExtensible) + capacity += 3; + + Node *node = new (pool->allocate(sizeof(Node))) Node(pool, id, op, unsigned(nInputs), + int(capacity)); + for (uint i = 0; i < capacity; ++i) + new (&node->m_inputs[int(i)]) Use(node); + for (size_t i = 0; i < nInputs; ++i) { + Q_ASSERT(inputs[i] != nullptr); + node->replaceInput(unsigned(i), inputs[i]); + } + + return node; +} + +void Node::addInput(MemoryPool *pool, Node *in) +{ + Q_ASSERT(in); + ++m_nInputs; + if (m_nInputs >= unsigned(m_inputs.size())) { + QQmlJS::FixedPoolArray<Use> oldInputs = m_inputs; + m_inputs = QQmlJS::FixedPoolArray<Use>(pool, int(m_nInputs + 3)); + for (Use &input : m_inputs) + new (&input) Use(this); + for (int i = 0, ei = oldInputs.size(); i != ei; ++i) { + Node *in = oldInputs[i].m_input; + oldInputs[i].set(nullptr); + m_inputs[i].set(in); + } + } + m_inputs.at(int(m_nInputs - 1)).set(in); +} + +void Node::removeInput(unsigned index) +{ + Q_ASSERT(index < inputCount()); + for (unsigned i = index, ei = inputCount(); i < ei - 1; ++i) + replaceInput(i, input(i + 1)); + trimInputCount(inputCount() - 1); +} + +void Node::removeInputs(unsigned start, unsigned count) +{ + for (unsigned idx = start; idx < start + count; ++idx) + m_inputs.at(int(idx)).set(nullptr); +} + +void Node::removeAllInputs() +{ + removeInputs(0, inputCount()); +} + +void Node::trimInputCount(unsigned newCount) +{ + unsigned currentCount = inputCount(); + if (newCount == currentCount) + return; + Q_ASSERT(newCount < currentCount); + removeInputs(newCount, currentCount - newCount); + m_nInputs = newCount; +} + +void Node::removeExceptionHandlerUse() +{ + for (Use* use = m_firstUse; use; use = use->m_next) { + if (use->m_input->opcode() == Meta::OnException) { + use->set(nullptr); + break; + } + } +} + +void Node::insertInput(Node::MemoryPool *pool, unsigned index, Node *newInput) +{ + Q_ASSERT(index < inputCount()); + addInput(pool, input(inputCount() - 1)); + for (unsigned i = inputCount() - 1; i > index; --i) + replaceInput(i, input(i - 1)); + replaceInput(index, newInput); +} + +void Node::replaceAllUsesWith(Node *replacement) +{ + for (Use *use = m_firstUse; use; ) { + Use *next = use->m_next; + const unsigned inIdx = use->inputIndex(); + use->user()->replaceInput(inIdx, replacement); + use = next; + } +} + +void Node::replaceUses(Node *newValueInput, Node *newEffectInput, Node *newControlInput) +{ + for (Use *use = m_firstUse; use; ) { + Use *next = use->m_next; + const Operation *inOp = use->user()->operation(); + const unsigned inIdx = use->inputIndex(); + if (inIdx < inOp->valueInputCount()) + use->user()->replaceInput(inIdx, newValueInput); + else if (inIdx < inOp->indexOfFirstControl()) + use->user()->replaceInput(inIdx, newEffectInput); + else + use->user()->replaceInput(inIdx, newControlInput); + use = next; + } +} + +Node *Node::firstValueUse() +{ + for (auto it = uses().begin(), eit = uses().end(); it != eit; ++it) { + if (it.isUsedAsValue()) + return *it; + } + return nullptr; +} + +Node::Node(MemoryPool *pool, Node::Id id, const Operation *op, unsigned nInputs, int capacity) + : m_op(op) + , m_inputs(pool, capacity) + , m_nInputs(nInputs) + , m_id(id) +{ +} + +NodeWorkList::NodeWorkList(const Graph *g) + : m_nodeState(g->nodeCount(), Unvisited) +{ m_worklist.reserve(64); } + +void NodeWorkList::reset() +{ + std::fill(m_nodeState.begin(), m_nodeState.end(), Unvisited); + m_worklist.clear(); + if (m_worklist.capacity() < 64) + m_worklist.reserve(64); +} + +NodeCollector::NodeCollector(const Graph *g, bool collectUses, bool skipFramestate) +{ + markReachable(g->endNode()); + for (size_t i = 0; i < m_reachable.size(); ++i) { // _reachable.size() is on purpose! + Node *n = m_reachable.at(i); + for (auto input : n->inputs()) { + if (input == nullptr) + continue; + if (isReachable(input->id())) + continue; + if (skipFramestate && input->opcode() == Meta::FrameState) + continue; + markReachable(input); + } + + if (collectUses) { + for (Node *use : n->uses()) { + if (use && !isReachable(use->id())) + markReachable(use); + } + } + } +} + +} // IR namespace +} // QV4 namespace +QT_END_NAMESPACE diff --git a/src/qml/jit/qv4node_p.h b/src/qml/jit/qv4node_p.h new file mode 100644 index 0000000000..76065fb1bc --- /dev/null +++ b/src/qml/jit/qv4node_p.h @@ -0,0 +1,626 @@ +/**************************************************************************** +** +** 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 QV4NODE_P_H +#define QV4NODE_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 <private/qqmljsmemorypool_p.h> +#include <private/qv4global_p.h> +#include <private/qv4operation_p.h> +#include "qv4util_p.h" + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace IR { + +class Use +{ + Q_DISABLE_COPY_MOVE(Use) + +public: + Use(Node *user) + : m_user(user) + {} + + ~Use() + { + if (m_input) + removeFromList(); + } + + operator Node *() const { return m_input; } + Node *input() const { return m_input; } + Node *user() const { return m_user; } + + inline void set(Node *newInput); + + void validate() const + { + Q_ASSERT(m_user); + if (m_input) { + if (m_prev != nullptr) + Q_ASSERT(*m_prev == this); + if (m_next) { + Q_ASSERT(m_next->m_input == m_input); + Q_ASSERT(m_next->m_prev == &m_next); + m_next->validate(); + } + } + } + + inline int inputIndex() const; + +protected: + friend class Node; + + void addToList(Use **list) { + validate(); + m_next = *list; + if (m_next) + m_next->m_prev = &m_next; + m_prev = list; + *list = this; + validate(); + } + + void removeFromList() { + validate(); + Use **newPrev = m_prev; + *newPrev = m_next; + m_prev = nullptr; + if (m_next) + m_next->m_prev = newPrev; + m_next = nullptr; + m_input = nullptr; + validate(); + } + +private: + Node *m_input = nullptr; + Node *m_user = nullptr; + Use *m_next = nullptr; + Use **m_prev = nullptr; +}; + +// A node represents an calculation, action, or marker in the graph. Each node has an operation, +// input dependencies and uses. The operation indicates what kind of node it is, e.g.: JSAdd, +// Constant, Region, and so on. Two nodes can have the same operation, but different inputs. +// For example, the expressions 1 + 2 and 3 + 4 will each have a node with an JSAdd operation +// (which is exactly the same operation for both nodes), but the nodes have different inputs (1, and +// 2 in the first expression, while the second operation has 3 and 4 as inputs). +class Node final +{ + Q_DISABLE_COPY_MOVE(Node) + +public: + using Id = uint32_t; + using MemoryPool = QQmlJS::MemoryPool; + class Inputs; + +public: + static Node *create(MemoryPool *pool, Id id, const Operation *op, size_t nInputs, + Node * const *inputs, bool inputsAreExtensible = false); + ~Node() = delete; + + inline bool isDead() const; + inline void kill(); + + Id id() const { return m_id; } + + const Operation *operation() const + { return m_op; } + + void setOperation(const Operation *op) + { m_op = op; } + + Operation::Kind opcode() const + { return operation()->kind(); } + + inline Inputs inputs() const; + void addInput(MemoryPool *pool, Node *in); + void removeInput(unsigned index); + void removeInputs(unsigned start, unsigned count); + void removeAllInputs(); + uint32_t inputCount() const + { return m_nInputs; } + void trimInputCount(unsigned newCount); + + void removeExceptionHandlerUse(); + + Node *input(unsigned idx) const + { + Q_ASSERT(idx < inputCount()); + return m_inputs.at(idx); + } + + Node *effectInput(unsigned effectIndex = 0) const + { + if (operation()->effectInputCount() == 0) + return nullptr; + Q_ASSERT(effectIndex < operation()->effectInputCount()); + return input(operation()->indexOfFirstEffect() + effectIndex); + } + + Node *controlInput(unsigned controlIndex = 0) const + { + if (operation()->controlInputCount() == 0) + return nullptr; + Q_ASSERT(controlIndex < operation()->controlInputCount()); + return input(operation()->indexOfFirstControl() + controlIndex); + } + + Node *frameStateInput() const + { + if (operation()->hasFrameStateInput()) + return input(operation()->indexOfFrameStateInput()); + return nullptr; + } + + void setFrameStateInput(Node *newFramestate) + { + if (operation()->hasFrameStateInput()) + replaceInput(operation()->indexOfFrameStateInput(), newFramestate); + } + + void insertInput(MemoryPool *pool, unsigned index, Node *newInput); + + void replaceInput(Node *oldIn, Node *newIn) + { + for (unsigned i = 0, ei = inputCount(); i != ei; ++i) { + if (input(i) == oldIn) + replaceInput(i, newIn); + } + } + + void replaceInput(unsigned idx, Node *newIn) + { + m_inputs[idx].set(newIn); + } + + class Uses + { + public: + explicit Uses(Node *node) + : m_node(node) + {} + + class const_iterator; + inline const_iterator begin() const; + inline const_iterator end() const; + + bool isEmpty() const; + + private: + Node *m_node; + }; + + Uses uses() { return Uses(this); } + bool hasUses() const { return m_firstUse != nullptr; } + unsigned useCount() const + { + unsigned cnt = 0; + for (Use *it = m_firstUse; it; it = it->m_next) + ++cnt; + return cnt; + } + void replaceAllUsesWith(Node *replacement); + void replaceUses(Node *newValueInput, Node *newEffectInput, Node *newControlInput); + + Node *firstValueUse(); + +private: // types and utility methods + friend class Use; + Node(MemoryPool *pool, Id id, const Operation *op, unsigned nInputs, int capacity); + +private: // fields + Use *m_firstUse = nullptr; + const Operation *m_op = nullptr; + QQmlJS::FixedPoolArray<Use> m_inputs; + unsigned m_nInputs = 0; + Id m_id = 0; +}; + +void Use::set(Node *newInput) +{ + if (m_input) + removeFromList(); + m_input = newInput; + if (newInput) + addToList(&newInput->m_firstUse); +} + +class Node::Inputs final +{ +public: + using value_type = Node *; + + class const_iterator; + inline const_iterator begin() const; + inline const_iterator end() const; + + bool empty() const + { return m_nInputs == 0; } + + unsigned count() const + { return m_nInputs; } + + explicit Inputs(const Use *inputs, unsigned nInputs) + : m_inputs(inputs), m_nInputs(nInputs) + {} + +private: + const Use *m_inputs = nullptr; + unsigned m_nInputs = 0; +}; + +class Node::Inputs::const_iterator final +{ +public: + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = Node *; + using pointer = const value_type *; + using reference = value_type &; + + Node *operator*() const + { return m_inputs->m_input; } + + bool operator==(const const_iterator &other) const + { return m_inputs == other.m_inputs; } + + bool operator!=(const const_iterator &other) const + { return !(*this == other); } + + const_iterator &operator++() + { ++m_inputs; return *this; } + + const_iterator& operator+=(difference_type offset) + { m_inputs += offset; return *this; } + + const_iterator operator+(difference_type offset) const + { return const_iterator(m_inputs + offset); } + + difference_type operator-(const const_iterator &other) const + { return m_inputs - other.m_inputs; } + +private: + friend class Node::Inputs; + + explicit const_iterator(const Use *inputs) + : m_inputs(inputs) + {} + + const Use *m_inputs; +}; + +Node::Inputs::const_iterator Node::Inputs::begin() const +{ return const_iterator(m_inputs); } + +Node::Inputs::const_iterator Node::Inputs::end() const +{ return const_iterator(m_inputs + m_nInputs); } + +Node::Inputs Node::inputs() const +{ + return Inputs(m_inputs.begin(), m_nInputs); +} + +class Node::Uses::const_iterator final +{ +public: + using iterator_category = std::forward_iterator_tag; + using difference_type = int; + using value_type = Node *; + using pointer = Node **; + using reference = Node *&; + + Node *operator*() const + { return m_current->user(); } + + bool operator==(const const_iterator &other) const + { return other.m_current == m_current; } + + bool operator!=(const const_iterator &other) const + { return other.m_current != m_current; } + + const_iterator &operator++() + { m_current = m_next; setNext(); return *this; } + + unsigned inputIndex() const + { return m_current->inputIndex(); } + + bool isUsedAsValue() const + { return inputIndex() < operator*()->operation()->valueInputCount(); } + + bool isUsedAsControl() const + { return operator*()->operation()->indexOfFirstControl() <= inputIndex(); } + +private: + friend class Node::Uses; + + const_iterator() = default; + + explicit const_iterator(Node* node) + : m_current(node->m_firstUse) + { setNext(); } + + void setNext() + { + if (m_current) + m_next = m_current->m_next; + else + m_next = nullptr; + } + +private: + Use *m_current = nullptr; + Use *m_next = nullptr; +}; + +Node::Uses::const_iterator Node::Uses::begin() const +{ return const_iterator(this->m_node); } + +Node::Uses::const_iterator Node::Uses::end() const +{ return const_iterator(); } + +int Use::inputIndex() const +{ + if (!m_user) + return -1; + return int(this - m_user->m_inputs.begin()); +} + +bool Node::isDead() const +{ + Inputs in = inputs(); + return !in.empty() && *in.begin() == nullptr; +} + +void Node::kill() +{ + removeAllInputs(); +} + +class NodeWorkList final +{ + enum State: uint8_t { + Unvisited = 0, + Queued, + Visited, + }; + +public: + NodeWorkList(const Graph *g); + + void reset(); + + bool enqueue(Node *n) + { + State &s = nodeState(n); + if (s == Queued || s == Visited) + return false; + + m_worklist.push_back(n); + s = Queued; + return true; + } + + void enqueue(const std::vector<Node *> &nodes) + { + m_worklist.insert(m_worklist.end(), nodes.begin(), nodes.end()); + for (Node *n : nodes) + nodeState(n) = Queued; + } + + void reEnqueue(Node *n) + { + if (!n) + return; + State &s = nodeState(n); + if (s == Queued) + return; + s = Queued; + m_worklist.push_back(n); + } + + void enqueueAllInputs(Node *n) + { + for (Node *input : n->inputs()) + enqueue(input); + } + + void reEnqueueAllInputs(Node *n) + { + for (Node *input : n->inputs()) + reEnqueue(input); + } + + void enqueueValueInputs(Node *n) + { + for (unsigned i = 0, ei = n->operation()->valueInputCount(); i != ei; ++i) + enqueue(n->input(i)); + } + + void enqueueEffectInputs(Node *n) + { + for (unsigned i = n->operation()->indexOfFirstEffect(), ei = n->operation()->effectInputCount(); i != ei; ++i) + enqueue(n->input(i)); + } + + void enqueueAllUses(Node *n) + { + for (Node *use : n->uses()) + enqueue(use); + } + + Node *dequeueNextNodeForVisiting() + { + while (!m_worklist.empty()) { + Node *n = m_worklist.back(); + m_worklist.pop_back(); + State &s = nodeState(n); + Q_ASSERT(s == Queued); + s = Visited; + return n; + } + + return nullptr; + } + + bool isVisited(Node *n) const + { return nodeState(n) == Visited; } + + bool isEmpty() const + { return m_worklist.empty(); } + + QString status(Node *n) const + { + QString s = QStringLiteral("status for node %1: ").arg(n->id()); + switch (nodeState(n)) { + case Queued: s += QLatin1String("queued"); break; + case Visited: s += QLatin1String("visited"); break; + case Unvisited: s += QLatin1String("unvisited"); break; + } + return s; + } + +private: + State &nodeState(Node *n) + { + const unsigned position(n->id()); + if (position >= m_nodeState.size()) + m_nodeState.resize(position + 1, Unvisited); + + return m_nodeState[position]; + } + + State nodeState(Node *n) const + { return m_nodeState[unsigned(n->id())]; } + +private: + std::vector<Node *> m_worklist; + std::vector<State> m_nodeState; +}; + +class NodeInfo +{ +public: + enum { NoInstructionOffset = -1 }; + +public: + NodeInfo() = default; + + Type type() const { return m_type; } + void setType(Type t) { m_type = t; } + + int currentInstructionOffset() const + { return m_currentInstructionOffset; } + + int nextInstructionOffset() const + { return m_nextInstructionOffset; } + + void setBytecodeOffsets(int current, int next) + { + Q_ASSERT(current != NoInstructionOffset); + Q_ASSERT(next != NoInstructionOffset); + m_currentInstructionOffset = current; + m_nextInstructionOffset = next; + } + +private: + Type m_type; + int m_currentInstructionOffset = NoInstructionOffset; + int m_nextInstructionOffset = NoInstructionOffset; +}; + +class NodeCollector +{ +public: + NodeCollector(const Graph *g, bool collectUses = false, bool skipFramestate = false); + + const std::vector<Node *> &reachable() const + { return m_reachable; } + + void sortById() + { + std::sort(m_reachable.begin(), m_reachable.end(), [](Node *n1, Node *n2) { + return n1->id() < n2->id(); + }); + } + + bool isReachable(Node::Id nodeId) const + { + if (nodeId >= Node::Id(m_isReachable.size())) + return false; + return m_isReachable.at(int(nodeId)); + } + + void markReachable(Node *node) + { + auto nodeId = node->id(); + m_reachable.push_back(node); + if (nodeId >= Node::Id(m_isReachable.size())) + m_isReachable.resize(int(nodeId + 1), false); + m_isReachable.setBit(int(nodeId)); + } + +private: + std::vector<Node *> m_reachable; + BitVector m_isReachable; +}; + +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4NODE_P_H diff --git a/src/qml/jit/qv4operation.cpp b/src/qml/jit/qv4operation.cpp new file mode 100644 index 0000000000..d10f8f19ac --- /dev/null +++ b/src/qml/jit/qv4operation.cpp @@ -0,0 +1,770 @@ +/**************************************************************************** +** +** 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 "qv4operation_p.h" +#include "qv4runtimesupport_p.h" + +QT_BEGIN_NAMESPACE +namespace QV4 { +namespace IR { + +OperationBuilder::OperationBuilder(QQmlJS::MemoryPool *graphPool) + : m_graphPool(graphPool) +{} + +OperationBuilder *OperationBuilder::create(QQmlJS::MemoryPool *pool) +{ + return pool->New<OperationBuilder>(pool); +} + +Operation *OperationBuilder::getConstant(Value v) +{ + Type t; + switch (v.type()) { + case Value::Undefined_Type: t = Type::undefinedType(); break; + case Value::Integer_Type: t = Type::int32Type(); break; + case Value::Boolean_Type: t = Type::booleanType(); break; + case Value::Null_Type: t = Type::nullType(); break; + case Value::Double_Type: t = Type::doubleType(); break; + case Value::Managed_Type: t = Type::objectType(); break; + default: + if (v.isEmpty()) + t = Type::emptyType(); + else + Q_UNREACHABLE(); + } + return OperationWithPayload<ConstantPayload>::create( + m_graphPool, Meta::Constant, 0, 0, 0, 1, 0, 0, t, Operation::NoFlags, + ConstantPayload(v)); +} + +Operation *OperationBuilder::getParam(unsigned index, const Function::StringId name) +{ + return OperationWithPayload<ParameterPayload>::create(m_graphPool, Meta::Parameter, + 1, 0, 0, + 1, 0, 0, + Type::anyType(), Operation::NoFlags, + ParameterPayload(index, name)); +} + +Operation *OperationBuilder::getRegion(unsigned nControlInputs) //### cache common operands in the static pool +{ + return Operation::create(m_graphPool, Meta::Region, + 0, 0, uint16_t(nControlInputs), + 0, 0, 1, + Type(), Operation::NoFlags); +} + +Operation *OperationBuilder::getPhi(unsigned nValueInputs) //### cache common operands in the static pool +{ + return Operation::create(m_graphPool, Meta::Phi, + uint16_t(nValueInputs), 0, 1, + 1, 0, 0, + Type::anyType(), Operation::NoFlags); +} + +Operation *OperationBuilder::getEffectPhi(unsigned nEffectInputs) //### cache common operands in the static pool +{ + return Operation::create(m_graphPool, Meta::EffectPhi, + 0, uint16_t(nEffectInputs), 1, + 0, 1, 0, + Type(), Operation::NoFlags); +} + +Operation *OperationBuilder::getUnwindDispatch(unsigned nContinuations, int unwindHandlerOffset, + int fallthroughSuccessor) +{ + return OperationWithPayload<UnwindDispatchPayload>::create( + m_graphPool, Meta::UnwindDispatch, + 0, 1, 1, 0, nContinuations, nContinuations, + Type(), Operation::NoFlags, + UnwindDispatchPayload(unwindHandlerOffset, + fallthroughSuccessor)); +} + +Operation *OperationBuilder::getHandleUnwind(int unwindHandlerOffset) +{ + return OperationWithPayload<HandleUnwindPayload>::create(m_graphPool, Meta::HandleUnwind, + 0, 1, 1, 0, 1, 1, + Type(), Operation::NoFlags, + HandleUnwindPayload(unwindHandlerOffset)); +} + +Operation *OperationBuilder::getFrameState(uint16_t frameSize) +{ + if (m_opFrameState == nullptr) + m_opFrameState = Operation::create(m_graphPool, Meta::FrameState, + frameSize, 0, 0, 0, 0, 1, + Type(), Operation::NoFlags); + else + Q_ASSERT(frameSize == m_opFrameState->valueInputCount()); + + return m_opFrameState; +} + +Operation *OperationBuilder::getStart(uint16_t outputCount) +{ + return Operation::create(m_graphPool, Meta::Start, + 0, 0, 0, + outputCount, 1, 1, + Type(), Operation::NoFlags); +} + +Operation *OperationBuilder::getEnd(uint16_t controlInputCount) +{ + return Operation::create(m_graphPool, Meta::End, + 0, 0, controlInputCount, + 0, 0, 0, + Type(), Operation::NoFlags); +} + +inline Operation *createOperation(Operation::Kind kind, QQmlJS::MemoryPool *staticPool) +{ + auto get = [&](uint16_t inValueCount, uint16_t inEffectCount, uint16_t inControlCount, + uint16_t outValueCount, uint16_t outEffectCount, uint16_t outControlCount, + Type (*typeCreator)(), uint8_t flags) { + return Operation::create(staticPool, kind, inValueCount, inEffectCount, inControlCount, + outValueCount, outEffectCount, outControlCount, typeCreator(), + flags); + }; + + using K = Operation::Kind; + using F = Operation::Flags; + const auto none = &Type::noneType; + const auto any = &Type::anyType; + const auto number = &Type::numberType; + const auto boolean = &Type::booleanType; + + switch (kind) { + case K::Undefined: + return OperationWithPayload<ConstantPayload>::create( + staticPool, K::Undefined, 0, 0, 0, 1, 0, 0, Type::undefinedType(), F::NoFlags, + ConstantPayload(Primitive::undefinedValue())); + case K::Empty: + return OperationWithPayload<ConstantPayload>::create( + staticPool, K::Constant, 0, 0, 0, 1, 0, 0, Type::emptyType(), Operation::NoFlags, + ConstantPayload(Primitive::emptyValue())); + case K::Engine: return get(1, 0, 0, 1, 0, 0, none, F::NoFlags); + case K::CppFrame: return get(1, 0, 0, 1, 0, 0, none, F::NoFlags); + case K::Function: return get(1, 0, 0, 1, 0, 0, none, F::NoFlags); + case K::Jump: return get(0, 0, 1, 0, 0, 1, none, F::NoFlags); + case K::Return: return get(1, 1, 1, 0, 0, 1, none, F::NoFlags); + case K::Branch: return get(1, 0, 1, 0, 0, 2, none, F::HasFrameStateInput | F::NeedsBytecodeOffsets); + case K::IfTrue: return get(0, 0, 1, 0, 0, 1, none, F::NoFlags); + case K::IfFalse: return get(0, 0, 1, 0, 0, 1, none, F::NoFlags); + case K::SelectOutput: return get(3, 1, 1, 1, 1, 1, any, F::NoFlags); + case K::Throw: return get(1, 1, 1, 0, 1, 1, any, F::NeedsBytecodeOffsets); + case K::OnException: return get(0, 0, 1, 0, 0, 1, none, F::NoFlags); + case K::ThrowReferenceError: return get(1, 1, 1, 0, 1, 1, any, F::NeedsBytecodeOffsets); + case K::UnwindToLabel: return get(2, 1, 1, 0, 1, 1, none, F::NoFlags); + case K::LoadRegExp: return get(1, 0, 0, 1, 0, 0, any, F::NoFlags); + case K::ScopedLoad: return get(2, 1, 0, 1, 1, 0, any, F::NoFlags); + case K::ScopedStore: return get(3, 1, 0, 0, 1, 0, none, F::NoFlags); + case K::JSLoadElement: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSGetLookup: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSLoadProperty: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSStoreElement: return get(3, 1, 1, 0, 1, 2, none, F::CanThrow); + case K::JSSetLookupStrict: return get(3, 1, 1, 0, 1, 2, none, F::CanThrow); + case K::JSSetLookupSloppy: return get(3, 1, 1, 0, 1, 2, none, F::CanThrow); + case K::JSStoreProperty: return get(3, 1, 1, 0, 1, 2, none, F::CanThrow); + case K::JSLoadName: return get(1, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSLoadGlobalLookup: return get(1, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSStoreNameSloppy: return get(2, 1, 1, 0, 1, 2, none, F::CanThrow); + case K::JSStoreNameStrict: return get(2, 1, 1, 0, 1, 2, none, F::CanThrow); + case K::JSLoadSuperProperty: return get(1, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSStoreSuperProperty: return get(2, 1, 1, 0, 1, 2, any, F::CanThrow); + case K::JSLoadClosure: return get(1, 1, 0, 1, 1, 0, any, F::Pure); + case K::JSGetIterator: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + + // special case: see GraphBuilder::generate_IteratorNext + case K::JSIteratorNext: return get(2, 1, 1, 2, 1, 1, any, F::NoFlags); + + // special case: see GraphBuilder::generate_IteratorNext + case K::JSIteratorNextForYieldStar: return get(3, 1, 1, 2, 1, 1, any, F::NoFlags); + + case K::JSIteratorClose: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSDeleteProperty: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSDeleteName: return get(1, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSIn: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSInstanceOf: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::QMLLoadQmlContextPropertyLookup: return get(1, 1, 1, 1, 1, 2, any, F::CanThrow); + + case K::JSEqual: return get(2, 1, 1, 1, 1, 2, boolean, F::CanThrow); + case K::JSGreaterThan: return get(2, 1, 1, 1, 1, 2, boolean, F::CanThrow); + case K::JSGreaterEqual: return get(2, 1, 1, 1, 1, 2, boolean, F::CanThrow); + case K::JSLessThan: return get(2, 1, 1, 1, 1, 2, boolean, F::CanThrow); + case K::JSLessEqual: return get(2, 1, 1, 1, 1, 2, boolean, F::CanThrow); + case K::JSStrictEqual: return get(2, 1, 1, 1, 1, 2, boolean, F::CanThrow); + + case K::JSAdd: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSSubtract: return get(2, 1, 1, 1, 1, 2, number, F::CanThrow); + case K::JSMultiply: return get(2, 1, 1, 1, 1, 2, number, F::CanThrow); + case K::JSDivide: return get(2, 1, 1, 1, 1, 2, number, F::CanThrow); + case K::JSModulo: return get(2, 1, 1, 1, 1, 2, number, F::CanThrow); + case K::JSExponentiate: return get(2, 1, 1, 1, 1, 2, number, F::CanThrow); + + case K::JSBitAnd: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSBitOr: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSBitXor: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSUnsignedShiftRight: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSShiftRight: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSShiftLeft: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + + case K::JSNegate: return get(1, 1, 1, 1, 1, 2, number, F::CanThrow); + case K::JSToNumber: return get(1, 1, 1, 1, 1, 2, number, F::CanThrow); + case K::Alloca: return get(1, 0, 0, 1, 0, 0, none, F::NoFlags); + + //### it is questionable if VAAlloc/VASeal need effect edges + case K::VAAlloc: return get(1, 1, 0, 1, 1, 0, none, F::NoFlags); + + case K::VAStore: return get(3, 0, 0, 1, 0, 0, none, F::NoFlags); + + case K::JSTypeofName: return get(1, 1, 0, 1, 1, 0, any, F::NoFlags); + case K::JSTypeofValue: return get(1, 0, 0, 1, 0, 0, any, F::Pure); + case K::JSDeclareVar: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSDestructureRestElement: return get(1, 1, 1, 1, 1, 2, any, F::CanThrow); + + case K::JSCreateCallContext: return get(0, 1, 1, 0, 1, 1, none, F::NoFlags); + case K::JSCreateCatchContext: return get(2, 1, 1, 1, 1, 1, none, F::NoFlags); + case K::JSCreateWithContext: return get(1, 1, 1, 1, 1, 1, any, F::NoFlags); + case K::JSCreateBlockContext: return get(1, 1, 1, 1, 1, 1, none, F::NoFlags); + case K::JSCloneBlockContext: return get(0, 1, 1, 0, 1, 1, none, F::NoFlags); + case K::JSCreateScriptContext: return get(1, 1, 1, 1, 1, 1, none, F::NoFlags); + case K::JSPopScriptContext: return get(0, 1, 1, 1, 1, 1, none, F::NoFlags); + case K::PopContext: return get(0, 1, 1, 0, 1, 1, none, F::NoFlags); + + case K::JSThisToObject: return get(1, 1, 1, 0, 1, 2, any, F::NoFlags); + case K::JSCreateMappedArgumentsObject: return get(0, 1, 0, 1, 1, 0, any, F::NoFlags); + case K::JSCreateUnmappedArgumentsObject: return get(0, 1, 0, 1, 1, 0, any, F::NoFlags); + case K::JSCreateRestParameter: return get(1, 0, 0, 1, 0, 0, any, F::NoFlags); + case K::JSLoadSuperConstructor: return get(1, 1, 1, 1, 1, 2, any, F::NoFlags); + case K::JSThrowOnNullOrUndefined: return get(1, 1, 1, 0, 1, 2, none, F::CanThrow); + case K::JSGetTemplateObject: return get(1, 0, 0, 1, 0, 0, any, F::NoFlags); + case K::StoreThis: return get(1, 1, 0, 1, 1, 0, any, F::NoFlags); + + case K::GetException: return get(0, 1, 0, 1, 1, 0, any, F::NoFlags); + case K::SetException: return get(1, 1, 0, 0, 1, 0, any, F::NoFlags); + + case K::ToObject: return get(1, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::ToBoolean: return get(1, 0, 0, 1, 0, 0, boolean, F::Pure); + + case K::IsEmpty: return get(1, 0, 0, 1, 0, 0, boolean, F::Pure); + + case K::BooleanNot: return get(1, 0, 0, 1, 0, 0, boolean, F::NoFlags); + case K::HasException: return get(1, 1, 0, 1, 1, 0, boolean, F::NoFlags); + + case K::Swap: return get(0, 0, 0, 0, 0, 0, none, F::NoFlags); + case K::Move: return get(1, 0, 0, 1, 0, 0, none, F::NoFlags); + + default: // Non-static operations: + return nullptr; + } +} + +Operation *OperationBuilder::staticOperation(Operation::Kind kind) +{ + static QAtomicPointer<Operation *> ops; + if (Operation **staticOps = ops.load()) + return staticOps[kind]; + + static QAtomicInt initializing = 0; + if (initializing.testAndSetOrdered(0, 1)) { + // This is safe now, because we can only run this piece of code once during the life time + // of the application as we can only change initializing from 0 to 1 once. + Operation **staticOps = new Operation *[Meta::KindsEnd]; + static QQmlJS::MemoryPool pool; + for (int i = 0; i < Meta::KindsEnd; ++i) + staticOps[i] = createOperation(Operation::Kind(i), &pool); + bool success = ops.testAndSetOrdered(nullptr, staticOps); + Q_ASSERT(success); + } else { + // Unfortunately we need to busy wait now until the other thread finishes the static + // initialization; + while (!ops.load()) {} + } + + return ops.load()[kind]; +} + +Operation *OperationBuilder::getJSVarArgsCall(Operation::Kind kind, uint16_t argc) +{ + return Operation::create(m_graphPool, kind, + argc, 1, 1, 1, 1, 2, + Type::anyType(), Operation::CanThrow); +} + +Operation *OperationBuilder::getJSTailCall(uint16_t argc) +{ + return Operation::create(m_graphPool, Meta::JSTailCall, + argc, 1, 1, 0, 0, 1, + Type(), Operation::NoFlags); +} + +Operation *OperationBuilder::getTailCall() +{ + // special varargs call, takes cppframe, engine, func, thisObject, argv, argc + return Operation::create(m_graphPool, Meta::TailCall, + 6, 1, 1, 0, 0, 1, + Type(), Operation::NoFlags); +} + +Operation *OperationBuilder::getCall(Operation::Kind callee) +{ + const bool canThrow = CallPayload::canThrow(callee); + const Type retTy = CallPayload::returnType(callee); + uint16_t nControlInputs = 0; + uint16_t nControlOutputs = 0; + if (canThrow) { + nControlInputs = 1; + nControlOutputs += 2; + } + if (CallPayload::changesContext(callee)) { + nControlInputs = 1; + nControlOutputs = std::max<uint16_t>(nControlInputs, 1); + } + if (callee == Meta::Throw || callee == Meta::ThrowReferenceError || + callee == Meta::JSIteratorNext || callee == Meta::JSIteratorNextForYieldStar) { + nControlInputs = 1; + nControlOutputs = 1; + } + Operation::Flags flags = Operation::NoFlags; + if (canThrow) + flags = Operation::Flags(flags | Operation::CanThrow); + if (CallPayload::isPure(callee)) + flags = Operation::Flags(flags | Operation::Pure); + const uint16_t nEffects = (flags & Operation::Pure) ? 0 : 1; + const uint16_t nValueOutputs = retTy.isNone() ? 0 : 1; + const uint16_t nValueInputs = CallPayload::argc(callee); + + return OperationWithPayload<CallPayload>::create( + m_graphPool, Meta::Call, + nValueInputs, nEffects, nControlInputs, + nValueOutputs, nEffects, nControlOutputs, + retTy, flags, + CallPayload(callee)); +} + +Operation *OperationBuilder::getVASeal(uint16_t nElements) +{ + return Operation::create(m_graphPool, Meta::VASeal, + nElements + 1, 1, 0, 1, 1, 0, + Type::anyType(), Operation::NoFlags); +} + +QString Operation::debugString() const +{ + switch (kind()) { + + case Meta::Constant: + return QStringLiteral("Constant[%1]").arg(ConstantPayload::get(*this)->debugString()); + case Meta::Parameter: + return QStringLiteral("Parameter[%1]").arg(ParameterPayload::get(*this)->debugString()); + case Meta::Call: + return QStringLiteral("Call[%1]").arg(CallPayload::get(*this)->debugString()); + case Meta::UnwindDispatch: + return QStringLiteral("UnwindDispatch[%1]").arg(UnwindDispatchPayload::get(*this) + ->debugString()); + case Meta::HandleUnwind: + return QStringLiteral("HandleUnwind[%1]").arg(HandleUnwindPayload::get(*this) + ->debugString()); + + default: + return QString::fromLatin1(QMetaEnum::fromType<Meta::OpKind>().valueToKey(kind())); + } +} + +QString ConstantPayload::debugString() const +{ + return debugString(m_value); +} + +QString ConstantPayload::debugString(QV4::Value v) +{ + if (v.isManaged()) + return QString().sprintf("Ptr: %p", v.heapObject()); + if (v.isEmpty()) + return QStringLiteral("empty"); + return v.toQStringNoThrow(); +} + +QString ParameterPayload::debugString() const +{ + return QStringLiteral("%1").arg(m_index); +} + +QString UnwindDispatchPayload::debugString() const +{ + return QStringLiteral("%1, %2").arg(QString::number(m_fallthroughSuccessor), + QString::number(m_unwindHandlerOffset)); +} + +QString HandleUnwindPayload::debugString() const +{ + return QStringLiteral("%1").arg(m_unwindHandlerOffset); +} + +static Type translateType(RuntimeSupport::ArgumentType t) +{ + switch (t) { + case RuntimeSupport::ArgumentType::Int: return Type::int32Type(); + case RuntimeSupport::ArgumentType::Bool: return Type::booleanType(); + case RuntimeSupport::ArgumentType::Void: return Type(); + case RuntimeSupport::ArgumentType::Engine: return Type::rawPointerType(); + case RuntimeSupport::ArgumentType::ValueRef: return Type::anyType(); + case RuntimeSupport::ArgumentType::ValueArray: return Type::anyType(); + case RuntimeSupport::ArgumentType::ReturnedValue: return Type::anyType(); + default: Q_UNREACHABLE(); + } +} + +template<template<typename Operation> class M /* MetaOperation */, typename ReturnValue> +static ReturnValue operateOnRuntimeCall(Operation::Kind kind, bool abortOnMissingCall = true) +{ + using K = Operation::Kind; + using R = Runtime; + + switch (kind) { + case K::Throw: return M<R::ThrowException>::doIt(); + case K::ThrowReferenceError: return M<R::ThrowReferenceError>::doIt(); + + case K::JSEqual: return M<R::CompareEqual>::doIt(); + case K::JSGreaterThan: return M<R::CompareGreaterThan>::doIt(); + case K::JSGreaterEqual: return M<R::CompareGreaterEqual>::doIt(); + case K::JSLessThan: return M<R::CompareLessThan>::doIt(); + case K::JSLessEqual: return M<R::CompareLessEqual>::doIt(); + case K::JSStrictEqual: return M<R::CompareStrictEqual>::doIt(); + + case K::JSBitAnd: return M<R::BitAnd>::doIt(); + case K::JSBitOr: return M<R::BitOr>::doIt(); + case K::JSBitXor: return M<R::BitXor>::doIt(); + case K::JSUnsignedShiftRight: return M<R::UShr>::doIt(); + case K::JSShiftRight: return M<R::Shr>::doIt(); + case K::JSShiftLeft: return M<R::Shl>::doIt(); + + case K::JSAdd: return M<R::Add>::doIt(); + case K::JSSubtract: return M<R::Sub>::doIt(); + case K::JSMultiply: return M<R::Mul>::doIt(); + case K::JSDivide: return M<R::Div>::doIt(); + case K::JSModulo: return M<R::Mod>::doIt(); + case K::JSExponentiate: return M<R::Exp>::doIt(); + + case K::ToBoolean: return M<R::ToBoolean>::doIt(); + case K::ToObject: return M<R::ToObject>::doIt(); + + case K::JSNegate: return M<R::UMinus>::doIt(); + case K::JSToNumber: return M<R::ToNumber>::doIt(); + + case K::JSLoadName: return M<R::LoadName>::doIt(); + case K::JSLoadElement: return M<R::LoadElement>::doIt(); + case K::JSStoreElement: return M<R::StoreElement>::doIt(); + case K::JSGetLookup: return M<R::GetLookup>::doIt(); + case K::JSSetLookupStrict: return M<R::SetLookupStrict>::doIt(); + case K::JSSetLookupSloppy: return M<R::SetLookupSloppy>::doIt(); + case K::JSLoadProperty: return M<R::LoadProperty>::doIt(); + case K::JSStoreProperty: return M<R::StoreProperty>::doIt(); + case K::JSLoadGlobalLookup: return M<R::LoadGlobalLookup>::doIt(); + case K::JSStoreNameSloppy: return M<R::StoreNameSloppy>::doIt(); + case K::JSStoreNameStrict: return M<R::StoreNameStrict>::doIt(); + case K::JSLoadSuperProperty: return M<R::LoadSuperProperty>::doIt(); + case K::JSStoreSuperProperty: return M<R::StoreSuperProperty>::doIt(); + case K::JSLoadClosure: return M<R::Closure>::doIt(); + case K::JSGetIterator: return M<R::GetIterator>::doIt(); + case K::JSIteratorNext: return M<R::IteratorNext>::doIt(); + case K::JSIteratorNextForYieldStar: return M<R::IteratorNextForYieldStar>::doIt(); + case K::JSIteratorClose: return M<R::IteratorClose>::doIt(); + case K::JSDeleteProperty: return M<R::DeleteProperty>::doIt(); + case K::JSDeleteName: return M<R::DeleteName>::doIt(); + case K::JSIn: return M<R::In>::doIt(); + case K::JSInstanceOf: return M<R::Instanceof>::doIt(); + case K::QMLLoadQmlContextPropertyLookup: return M<R::LoadQmlContextPropertyLookup>::doIt(); + + case K::JSTypeofName: return M<R::TypeofName>::doIt(); + case K::JSTypeofValue: return M<R::TypeofValue>::doIt(); + case K::JSDeclareVar: return M<R::DeclareVar>::doIt(); + case K::JSDestructureRestElement: return M<R::DestructureRestElement>::doIt(); + case K::JSThisToObject: return M<R::ConvertThisToObject>::doIt(); + case K::JSCreateMappedArgumentsObject: return M<R::CreateMappedArgumentsObject>::doIt(); + case K::JSCreateUnmappedArgumentsObject: return M<R::CreateUnmappedArgumentsObject>::doIt(); + case K::JSCreateRestParameter: return M<R::CreateRestParameter>::doIt(); + case K::JSLoadSuperConstructor: return M<R::LoadSuperConstructor>::doIt(); + case K::JSThrowOnNullOrUndefined: return M<R::ThrowOnNullOrUndefined>::doIt(); + + case K::JSCreateCallContext: return M<R::PushCallContext>::doIt(); + case K::JSCreateCatchContext: return M<R::PushCatchContext>::doIt(); + case K::JSCreateWithContext: return M<R::PushWithContext>::doIt(); + case K::JSCreateBlockContext: return M<R::PushBlockContext>::doIt(); + case K::JSCloneBlockContext: return M<R::CloneBlockContext>::doIt(); + case K::JSCreateScriptContext: return M<R::PushScriptContext>::doIt(); + case K::JSPopScriptContext: return M<R::PopScriptContext>::doIt(); + + case K::LoadRegExp: return M<R::RegexpLiteral>::doIt(); + case K::JSGetTemplateObject: return M<R::GetTemplateObject>::doIt(); + + case K::JSCallName: return M<R::CallName>::doIt(); + case K::JSCallValue: return M<R::CallValue>::doIt(); + case K::JSCallElement: return M<R::CallElement>::doIt(); + case K::JSCallLookup: return M<R::CallPropertyLookup>::doIt(); + case K::JSCallProperty: return M<R::CallProperty>::doIt(); + case K::JSCallGlobalLookup: return M<R::CallGlobalLookup>::doIt(); + case K::JSCallPossiblyDirectEval: return M<R::CallPossiblyDirectEval>::doIt(); + case K::JSCallWithReceiver: return M<R::CallWithReceiver>::doIt(); + case K::JSDefineObjectLiteral: return M<R::ObjectLiteral>::doIt(); + case K::JSDefineArray: return M<R::ArrayLiteral>::doIt(); + case K::JSCallWithSpread: return M<R::CallWithSpread>::doIt(); + case K::JSConstruct: return M<R::Construct>::doIt(); + case K::JSConstructWithSpread: return M<R::ConstructWithSpread>::doIt(); + case K::JSTailCall: return M<R::TailCall>::doIt(); + case K::JSCreateClass: return M<R::CreateClass>::doIt(); + default: + if (abortOnMissingCall) + Q_UNREACHABLE(); + else + return ReturnValue(); + } +} + +template<typename Method> +struct IsRuntimeMethodOperation +{ + static constexpr bool doIt() { return true; } +}; + +bool CallPayload::isRuntimeCall(Operation::Kind m) +{ + return operateOnRuntimeCall<IsRuntimeMethodOperation, bool>(m, false); +} + +QString CallPayload::debugString() const +{ + return QString::fromLatin1(QMetaEnum::fromType<Meta::OpKind>().valueToKey(m_callee)); +} + +template<typename Method> +struct MethodArgcOperation +{ + static constexpr unsigned doIt() { return RuntimeSupport::argumentCount<Method>(); } +}; + +unsigned CallPayload::argc(Operation::Kind callee) +{ + return operateOnRuntimeCall<MethodArgcOperation, unsigned>(callee); +} + +template<typename Method> struct MethodArg1TyOperation { static constexpr RuntimeSupport::ArgumentType doIt() { return RuntimeSupport::arg1Type<Method>(); } }; +template<typename Method> struct MethodArg2TyOperation { static constexpr RuntimeSupport::ArgumentType doIt() { return RuntimeSupport::arg2Type<Method>(); } }; +template<typename Method> struct MethodArg3TyOperation { static constexpr RuntimeSupport::ArgumentType doIt() { return RuntimeSupport::arg3Type<Method>(); } }; +template<typename Method> struct MethodArg4TyOperation { static constexpr RuntimeSupport::ArgumentType doIt() { return RuntimeSupport::arg4Type<Method>(); } }; +template<typename Method> struct MethodArg5TyOperation { static constexpr RuntimeSupport::ArgumentType doIt() { return RuntimeSupport::arg5Type<Method>(); } }; +template<typename Method> struct MethodArg6TyOperation { static constexpr RuntimeSupport::ArgumentType doIt() { return RuntimeSupport::arg6Type<Method>(); } }; + +static RuntimeSupport::ArgumentType untranslatedArgumentType(Operation::Kind m, unsigned arg) +{ + if (m == Meta::JSTailCall) { + if (arg < 4) + return RuntimeSupport::ArgumentType::ValueRef; + else + return RuntimeSupport::ArgumentType::Invalid; + } + + switch (arg) { + case 0: return operateOnRuntimeCall<MethodArg1TyOperation, RuntimeSupport::ArgumentType>(m); + case 1: return operateOnRuntimeCall<MethodArg2TyOperation, RuntimeSupport::ArgumentType>(m); + case 2: return operateOnRuntimeCall<MethodArg3TyOperation, RuntimeSupport::ArgumentType>(m); + case 3: return operateOnRuntimeCall<MethodArg4TyOperation, RuntimeSupport::ArgumentType>(m); + case 4: return operateOnRuntimeCall<MethodArg5TyOperation, RuntimeSupport::ArgumentType>(m); + case 5: return operateOnRuntimeCall<MethodArg6TyOperation, RuntimeSupport::ArgumentType>(m); + default: return RuntimeSupport::ArgumentType::Invalid; + } +} + +bool CallPayload::needsStorageOnJSStack(Operation::Kind m, unsigned arg, const Operation *op, + Type nodeType) +{ + auto argTy = untranslatedArgumentType(m, arg); + if (argTy == RuntimeSupport::ArgumentType::ValueArray) + return true; + if (argTy != RuntimeSupport::ArgumentType::ValueRef) + return false; + + if (op->kind() == Meta::Constant) + return true; + + return !nodeType.isObject() && !nodeType.isRawPointer() && !nodeType.isAny(); +} + +template<typename Method> +struct MethodRetTyOperation +{ + static constexpr RuntimeSupport::ArgumentType doIt() { return RuntimeSupport::retType<Method>(); } +}; + +Type CallPayload::returnType(Operation::Kind m) +{ + if (m == Meta::JSTailCall) + return Type(); + + auto t = operateOnRuntimeCall<MethodRetTyOperation, RuntimeSupport::ArgumentType>(m); + return translateType(t); +} + +static int firstArgumentPositionForType(Operation::Kind m, RuntimeSupport::ArgumentType type) +{ + if (operateOnRuntimeCall<MethodArg1TyOperation, RuntimeSupport::ArgumentType>(m) == type) + return 1; + if (operateOnRuntimeCall<MethodArg2TyOperation, RuntimeSupport::ArgumentType>(m) == type) + return 2; + if (operateOnRuntimeCall<MethodArg3TyOperation, RuntimeSupport::ArgumentType>(m) == type) + return 3; + if (operateOnRuntimeCall<MethodArg4TyOperation, RuntimeSupport::ArgumentType>(m) == type) + return 4; + if (operateOnRuntimeCall<MethodArg5TyOperation, RuntimeSupport::ArgumentType>(m) == type) + return 5; + if (operateOnRuntimeCall<MethodArg6TyOperation, RuntimeSupport::ArgumentType>(m) == type) + return 6; + return -1; +} + +unsigned CallPayload::varArgsStart(Operation::Kind m) +{ + if (m == Meta::JSTailCall) + return 4 - 1; + + int pos = firstArgumentPositionForType(m, RuntimeSupport::ArgumentType::ValueArray) - 1; + Q_ASSERT(pos >= 0); + return pos; +} + +bool CallPayload::isVarArgsCall(Operation::Kind m) +{ + if (m == Meta::JSTailCall) + return true; + if (lastArgumentIsOutputValue(m)) + return false; + return firstArgumentPositionForType(m, RuntimeSupport::ArgumentType::ValueArray) != -1; +} + +bool CallPayload::isVarArgsCall() const +{ + return isVarArgsCall(m_callee); +} + +template<typename Method> +struct MethodsLastArgumentIsOutputValue +{ + static constexpr bool doIt() { return Method::lastArgumentIsOutputValue; } +}; + +bool CallPayload::lastArgumentIsOutputValue(Operation::Kind m) +{ + return operateOnRuntimeCall<MethodsLastArgumentIsOutputValue, bool>(m); +} + +template<typename Method> +struct MethodChangesContext +{ + static constexpr bool doIt() { return Method::changesContext; } +}; + +bool CallPayload::changesContext(Operation::Kind m) +{ + return operateOnRuntimeCall<MethodChangesContext, bool>(m); +} + +template<typename Method> +struct MethodIsPure +{ + static constexpr bool doIt() { return Method::pure; } +}; + +bool CallPayload::isPure(Operation::Kind m) +{ + return operateOnRuntimeCall<MethodIsPure, bool>(m); +} + +template<typename Method> +struct MethodCanThrow +{ + static constexpr bool doIt() { return Method::throws; } +}; + +bool CallPayload::canThrow(Operation::Kind m) +{ + switch (m) { + case Meta::Throw: Q_FALLTHROUGH(); + case Meta::ThrowReferenceError: + // the execution path following these instructions is already linked up to the exception handler + return false; + case Meta::JSIteratorNext: Q_FALLTHROUGH(); + case Meta::JSIteratorNextForYieldStar: + // special case: see GraphBuilder::generate_IteratorNext + return false; + default: + return operateOnRuntimeCall<MethodCanThrow, bool>(m); + } +} + +bool CallPayload::takesEngineAsArg(Operation::Kind m, int arg) +{ + return untranslatedArgumentType(m, arg) == RuntimeSupport::ArgumentType::Engine; +} + +bool CallPayload::takesFunctionAsArg(Operation::Kind m, int arg) +{ + return untranslatedArgumentType(m, arg) == RuntimeSupport::ArgumentType::Function; +} + +bool CallPayload::takesFrameAsArg(Operation::Kind m, int arg) +{ + return untranslatedArgumentType(m, arg) == RuntimeSupport::ArgumentType::Frame; +} + +template<typename Method> +struct GetMethodPtr +{ + static constexpr void *doIt() { return reinterpret_cast<void *>(&Method::call); } +}; + +void *CallPayload::getMethodPtr(Operation::Kind m) +{ + return operateOnRuntimeCall<GetMethodPtr, void *>(m); +} + +} // IR namespace +} // QV4 namespace +QT_END_NAMESPACE diff --git a/src/qml/jit/qv4operation_p.h b/src/qml/jit/qv4operation_p.h new file mode 100644 index 0000000000..43214023e8 --- /dev/null +++ b/src/qml/jit/qv4operation_p.h @@ -0,0 +1,567 @@ +/**************************************************************************** +** +** 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 QV4OPERATION_P_H +#define QV4OPERATION_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 <private/qv4ir_p.h> +#include <private/qqmljsmemorypool_p.h> + +#include <QtCore/qatomic.h> + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace IR { + +namespace Meta { +enum OpKind: uint16_t { + FrameState, + Start, + End, + + Undefined, + Constant, + Parameter, + Empty, + Engine, + CppFrame, + Function, + + Jump, + Return, + JSTailCall, + TailCall, + Branch, + IfTrue, + IfFalse, + Region, + OnException, + Phi, + EffectPhi, + SelectOutput, + UnwindDispatch, + UnwindToLabel, + HandleUnwind, + Throw, + ThrowReferenceError, + + Call, + + LoadRegExp, + ScopedLoad, + ScopedStore, + + JSLoadElement, + JSStoreElement, + JSGetLookup, + JSSetLookupStrict, + JSSetLookupSloppy, + JSLoadProperty, + JSStoreProperty, + JSLoadName, + JSLoadGlobalLookup, + JSStoreNameSloppy, + JSStoreNameStrict, + JSLoadSuperProperty, + JSStoreSuperProperty, + JSLoadClosure, + JSGetIterator, + JSIteratorNext, + JSIteratorNextForYieldStar, + JSIteratorClose, + JSDeleteProperty, + JSDeleteName, + JSIn, + JSInstanceOf, + + /* ok, these are qml object ops, but we don't care for now and treat them as JS */ + QMLLoadQmlContextPropertyLookup, + QMLCallQmlContextPropertyLookup, + + JSEqual, + JSGreaterThan, + JSGreaterEqual, + JSLessThan, + JSLessEqual, + JSStrictEqual, + + JSAdd, + JSSubtract, + JSMultiply, + JSDivide, + JSModulo, + JSExponentiate, + + JSBitAnd, + JSBitOr, + JSBitXor, + JSUnsignedShiftRight, + JSShiftRight, + JSShiftLeft, + + JSNegate, + JSToNumber, + + JSCallName, + JSCallValue, + JSCallElement, + JSCallProperty, + JSCallLookup, + JSCallGlobalLookup, + JSCallPossiblyDirectEval, + JSCallWithReceiver, + JSCallWithSpread, + JSDefineObjectLiteral, + JSDefineArray, + JSCreateClass, + JSConstruct, + JSConstructWithSpread, + + JSTypeofName, + JSTypeofValue, + JSDeclareVar, + JSDestructureRestElement, + JSThisToObject, + JSCreateMappedArgumentsObject, + JSCreateUnmappedArgumentsObject, + JSCreateRestParameter, + JSLoadSuperConstructor, + JSThrowOnNullOrUndefined, + JSGetTemplateObject, + StoreThis, + + JSCreateCallContext, + JSCreateCatchContext, + JSCreateWithContext, + JSCreateBlockContext, + JSCloneBlockContext, + JSCreateScriptContext, + JSPopScriptContext, + PopContext, + + GetException, + SetException, + + ToObject, + ToBoolean, + + //### do we need this? Or should a later phase generate JumpIsEmpty? + IsEmpty, + + Alloca, + VAAlloc, + VAStore, + VASeal, + + BooleanNot, + HasException, + + // Low level, used by the register allocator and stack allocator: + Swap, + Move, + KindsEnd +}; +Q_NAMESPACE +Q_ENUM_NS(OpKind) +} // namespace Ops + +class Operation +{ + Q_DISABLE_COPY_MOVE(Operation) + +public: + using Kind = Meta::OpKind; + + enum Flags: uint8_t { + NoFlags = 0, + ThrowsFlag = 1 << 0, + Pure = 1 << 1, // no read/write side effect, cannot throw, cannot deopt, and is idempotent + NeedsBytecodeOffsets = 1 << 2, + + CanThrow = ThrowsFlag | NeedsBytecodeOffsets, + + HasFrameStateInput = 1 << 3, + }; + +public: + static Operation *create(QQmlJS::MemoryPool *pool, Kind kind, uint16_t inValueCount, + uint16_t inEffectCount, uint16_t inControlCount, + uint16_t outValueCount, uint16_t outEffectCount, + uint16_t outControlCount, Type type, uint8_t flags) + { + return pool->New<Operation>(kind, inValueCount, inEffectCount, inControlCount, + outValueCount, outEffectCount, outControlCount, + type, Flags(flags)); + } + + Kind kind() const + { return m_kind; } + + bool isConstant() const + { + switch (kind()) { + case Meta::Undefined: Q_FALLTHROUGH(); + case Meta::Constant: + case Meta::Empty: + return true; + default: + return false; + } + } + + QString debugString() const; + + uint16_t valueInputCount() const { return m_inValueCount; } + uint16_t effectInputCount() const { return m_inEffectCount; } + uint16_t controlInputCount() const { return m_inControlCount; } + uint16_t valueOutputCount() const { return m_outValueCount; } + uint16_t effectOutputCount() const { return m_outEffectCount; } + uint16_t controlOutputCount() const { return m_outControlCount; } + + unsigned indexOfFirstEffect() const { return m_inValueCount; } + unsigned indexOfFirstControl() const { return m_inValueCount + m_inEffectCount; } + unsigned indexOfFrameStateInput() const + { + return hasFrameStateInput() ? indexOfFirstControl() + m_inControlCount + : std::numeric_limits<unsigned>::max(); + } + + Type type() const + { return m_type; } + + bool canThrow() const + { return m_flags & ThrowsFlag; } + + bool isPure() const + { return m_flags & Pure; } + + bool needsBytecodeOffsets() const + { return m_flags & NeedsBytecodeOffsets; } + + bool hasFrameStateInput() const + { return m_flags & HasFrameStateInput; } + + unsigned totalInputCount() const + { + return valueInputCount() + effectInputCount() + controlInputCount() + + (hasFrameStateInput() ? 1 : 0); + } + unsigned totalOutputCount() const { return valueOutputCount() + effectOutputCount() + controlOutputCount(); } + +protected: + friend class QQmlJS::MemoryPool; + Operation(Kind kind, + uint16_t inValueCount, uint16_t inEffectCount, uint16_t inControlCount, + uint16_t outValueCount, uint16_t outEffectCount, uint16_t outControlCount, + Type type, uint8_t flags) + : m_kind(kind) + , m_inValueCount(inValueCount) + , m_inEffectCount(inEffectCount) + , m_inControlCount(inControlCount) + , m_outValueCount(outValueCount) + , m_outEffectCount(outEffectCount) + , m_outControlCount(outControlCount) + , m_type(type) + , m_flags(Flags(flags)) + { + } + + ~Operation() = default; + +private: + Kind m_kind; + uint16_t m_inValueCount; + uint16_t m_inEffectCount; + uint16_t m_inControlCount; + uint16_t m_outValueCount; + uint16_t m_outEffectCount; + uint16_t m_outControlCount; + Type m_type; + Flags m_flags; +}; + +template <typename Payload> +class OperationWithPayload: public Operation +{ +public: + static OperationWithPayload *create(QQmlJS::MemoryPool *pool, Kind kind, + uint16_t inValueCount, uint16_t inEffectCount, uint16_t inControlCount, + uint16_t outValueCount, uint16_t outEffectCount, uint16_t outControlCount, + Type type, Flags flags, Payload payload) + { + return pool->New<OperationWithPayload>(kind, inValueCount, inEffectCount, inControlCount, + outValueCount, outEffectCount, outControlCount, + type, flags, payload); + } + + const Payload &payload() const + { return m_payload; } + +protected: + friend class QQmlJS::MemoryPool; + OperationWithPayload(Kind kind, + uint16_t inValueCount, uint16_t inEffectCount, uint16_t inControlCount, + uint16_t outValueCount, uint16_t outEffectCount, uint16_t outControlCount, + Type type, Flags flags, Payload payload) + : Operation(kind, + inValueCount, inEffectCount, inControlCount, + outValueCount, outEffectCount, outControlCount, + type, flags) + , m_payload(payload) + {} + + ~OperationWithPayload() = default; + +private: + Payload m_payload; +}; + +class ConstantPayload +{ +public: + explicit ConstantPayload(QV4::Value v) + : m_value(v) + {} + + QV4::Value value() const + { return m_value; } + + static const ConstantPayload *get(const Operation &op) + { + if (op.kind() != Meta::Constant) + return nullptr; + + return &static_cast<const OperationWithPayload<ConstantPayload>&>(op).payload(); + } + + QString debugString() const; + static QString debugString(QV4::Value v); + +private: + QV4::Value m_value; +}; + +class ParameterPayload +{ +public: + ParameterPayload(size_t index, Function::StringId stringId) + : m_index(index) + , m_stringId(stringId) + {} + + size_t parameterIndex() const + { return m_index; } + + Function::StringId stringId() const + { return m_stringId; } + + static const ParameterPayload *get(const Operation &op) + { + if (op.kind() != Meta::Parameter) + return nullptr; + + return &static_cast<const OperationWithPayload<ParameterPayload>&>(op).payload(); + } + + QString debugString() const; + +private: + size_t m_index; + Function::StringId m_stringId; +}; + +class CallPayload +{ +public: + CallPayload(Operation::Kind callee) + : m_callee(callee) + {} + + static const CallPayload *get(const Operation &op) + { + if (op.kind() != Meta::Call) + return nullptr; + + return &static_cast<const OperationWithPayload<CallPayload>&>(op).payload(); + } + + static bool isRuntimeCall(Operation::Kind m); + + Operation::Kind callee() const { return m_callee; } + QString debugString() const; + + unsigned argc() const { return argc(m_callee); } + static unsigned argc(Operation::Kind callee); + static bool needsStorageOnJSStack(Operation::Kind m, unsigned arg, const Operation *op, + Type nodeType); + static Type returnType(Operation::Kind m); + static int engineArgumentPosition(Operation::Kind m); + static int functionArgumentPosition(Operation::Kind m); + + static constexpr unsigned NoVarArgs = std::numeric_limits<unsigned>::max(); + static unsigned varArgsStart(Operation::Kind m); + static bool isVarArgsCall(Operation::Kind m); + bool isVarArgsCall() const; + static bool lastArgumentIsOutputValue(Operation::Kind m); + static bool changesContext(Operation::Kind m); + static bool isPure(Operation::Kind m); + static bool canThrow(Operation::Kind m); + static bool takesEngineAsArg(Operation::Kind m, int arg); + static bool takesFunctionAsArg(Operation::Kind m, int arg); + static bool takesFrameAsArg(Operation::Kind m, int arg); + static void *getMethodPtr(Operation::Kind m); + +private: + Operation::Kind m_callee; +}; + +class UnwindDispatchPayload +{ +public: + UnwindDispatchPayload(int unwindHandlerOffset, int fallthroughSuccessor) + : m_unwindHandlerOffset(unwindHandlerOffset) + , m_fallthroughSuccessor(fallthroughSuccessor) + {} + + int unwindHandlerOffset() const + { return m_unwindHandlerOffset; } + + int fallthroughSuccessor() const //### unused... + { return m_fallthroughSuccessor; } + + static const UnwindDispatchPayload *get(const Operation &op) + { + if (op.kind() != Meta::UnwindDispatch) + return nullptr; + + return &static_cast<const OperationWithPayload<UnwindDispatchPayload>&>(op).payload(); + } + + QString debugString() const; + +private: + int m_unwindHandlerOffset; + int m_fallthroughSuccessor; +}; + +class HandleUnwindPayload +{ +public: + HandleUnwindPayload(int unwindHandlerOffset) + : m_unwindHandlerOffset(unwindHandlerOffset) + {} + + int unwindHandlerOffset() const + { return m_unwindHandlerOffset; } + + static const HandleUnwindPayload *get(const Operation &op) + { + if (op.kind() != Meta::HandleUnwind) + return nullptr; + + return &static_cast<const OperationWithPayload<HandleUnwindPayload>&>(op).payload(); + } + + QString debugString() const; + +private: + int m_unwindHandlerOffset; +}; + +class OperationBuilder +{ + Q_DISABLE_COPY_MOVE(OperationBuilder) + + friend class QQmlJS::MemoryPool; + OperationBuilder(QQmlJS::MemoryPool *graphPool); + +public: + static OperationBuilder *create(QQmlJS::MemoryPool *pool); + ~OperationBuilder() = delete; + + Operation *getConstant(QV4::Value v); + Operation *getFrameState(uint16_t frameSize); + Operation *getStart(uint16_t outputCount); + Operation *getEnd(uint16_t controlInputCount); + Operation *getParam(unsigned index, Function::StringId name); + Operation *getRegion(unsigned nControlInputs); + Operation *getPhi(unsigned nValueInputs); + Operation *getEffectPhi(unsigned nEffectInputs); + Operation *getUnwindDispatch(unsigned nControlOutputs, int unwindHandlerOffset, int fallthroughSuccessor); + Operation *getHandleUnwind(int unwindHandlerOffset); + + template<Operation::Kind kind> + Operation *get() { + return staticOperation(kind); + } + + Operation *getVASeal(uint16_t nElements); + + Operation *getJSVarArgsCall(Operation::Kind kind, uint16_t argc); + Operation *getJSTailCall(uint16_t argc); + Operation *getTailCall(); + + Operation *getCall(Operation::Kind callee); + +private: + QQmlJS::MemoryPool *m_graphPool; // used to store per-graph nodes + Operation *m_opFrameState = nullptr; + static Operation *staticOperation(Operation::Kind kind); +}; + +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4OPERATION_P_H diff --git a/src/qml/jit/qv4runtimesupport_p.h b/src/qml/jit/qv4runtimesupport_p.h new file mode 100644 index 0000000000..0dc6022331 --- /dev/null +++ b/src/qml/jit/qv4runtimesupport_p.h @@ -0,0 +1,255 @@ +/**************************************************************************** +** +** 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 QV4RUNTIMESUPPORT_P_H +#define QV4RUNTIMESUPPORT_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 <qv4runtimeapi_p.h> + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace IR { +namespace RuntimeSupport { + +template <typename T> +struct CountArguments { + static constexpr unsigned count = 0; +}; +template <typename RetTy, typename... Args> +struct CountArguments<RetTy (*)(Args... args)> { + static constexpr unsigned count = sizeof...(Args) ; +}; + +template<typename M> +static constexpr unsigned argumentCount() { + using type = decltype(&M::call); + return CountArguments<type>::count; +} + +enum class ArgumentType { + Invalid, + Engine, + Frame, + Function, + ValueRef, + ValueArray, + ReturnedValue, + Int, + Bool, + Void, +}; + + +template <typename T> +struct JavaScriptType +{ + // No default type. We want to make sure everything we do is actually recognized. +}; + +template <typename T> +struct ReturnValue +{ + // No default type. +}; + +template <int I, typename T> +struct Argument +{ + // For simplicity, we add a default here. Otherwise we would need to spell out more + // combinations of I and number of arguments of T. + static constexpr ArgumentType type = ArgumentType::Invalid; +}; + +template <typename RetTy, typename T, typename... Args> +struct Argument<1, RetTy (*)(T, Args... args)> { + static constexpr ArgumentType type = JavaScriptType<T>::type; +}; + +template <typename RetTy, typename Arg1, typename T, typename... Args> +struct Argument<2, RetTy (*)(Arg1, T, Args... args)> { + static constexpr ArgumentType type = JavaScriptType<T>::type; +}; + +template <typename RetTy, typename Arg1, typename Arg2, typename T, + typename... Args> +struct Argument<3, RetTy (*)(Arg1, Arg2, T, Args... args)> { + static constexpr ArgumentType type = JavaScriptType<T>::type; +}; + +template <typename RetTy, typename Arg1, typename Arg2, + typename Arg3, typename T, typename... Args> +struct Argument<4, RetTy (*)(Arg1, Arg2, Arg3, T, Args... args)> { + static constexpr ArgumentType type = JavaScriptType<T>::type; +}; + +template <typename RetTy, typename Arg1, typename Arg2, + typename Arg3, typename Arg4, typename T, typename... Args> +struct Argument<5, RetTy (*)(Arg1, Arg2, Arg3, Arg4, T, Args... args)> { + static constexpr ArgumentType type = JavaScriptType<T>::type; +}; + +template <typename RetTy, typename Arg1, typename Arg2, + typename Arg3, typename Arg4, typename Arg5, typename T, typename... Args> +struct Argument<6, RetTy (*)(Arg1, Arg2, Arg3, Arg4, Arg5, T, Args... args)> { + static constexpr ArgumentType type = JavaScriptType<T>::type; +}; + +template <typename RetTy, typename... Args> +struct ReturnValue<RetTy (*)(Args... args)> { + static constexpr ArgumentType type = JavaScriptType<RetTy>::type; +}; + +template<> +struct JavaScriptType<QV4::ExecutionEngine *> +{ + static constexpr ArgumentType type = ArgumentType::Engine; +}; + +template<> +struct JavaScriptType<QV4::CppStackFrame *> +{ + static constexpr ArgumentType type = ArgumentType::Frame; +}; + +template<> +struct JavaScriptType<QV4::Function *> +{ + static constexpr ArgumentType type = ArgumentType::Function; +}; + +template<> +struct JavaScriptType<const QV4::Value &> +{ + static constexpr ArgumentType type = ArgumentType::ValueRef; +}; + +template<> +// We need to pass Value * in order to match a parmeter Value[]. +struct JavaScriptType<QV4::Value *> +{ + static constexpr ArgumentType type = ArgumentType::ValueArray; +}; + +template<> +struct JavaScriptType<int> +{ + static constexpr ArgumentType type = ArgumentType::Int; +}; + +template<> +struct JavaScriptType<QV4::Bool> +{ + static constexpr ArgumentType type = ArgumentType::Bool; +}; + +template<> +struct JavaScriptType<QV4::ReturnedValue> +{ + static constexpr ArgumentType type = ArgumentType::ReturnedValue; +}; + +template<> +struct JavaScriptType<void> +{ + static constexpr ArgumentType type = ArgumentType::Void; +}; + +template<typename M> +static constexpr ArgumentType retType() { + using Type = decltype(&M::call); + return ReturnValue<Type>::type; +} + +template<typename M> +static constexpr ArgumentType arg1Type() { + using Type = decltype(&M::call); + return Argument<1, Type>::type; +} + +template<typename M> +static constexpr ArgumentType arg2Type() { + using Type = decltype(&M::call); + return Argument<2, Type>::type; +} + +template<typename M> +static constexpr ArgumentType arg3Type() { + using Type = decltype(&M::call); + return Argument<3, Type>::type; +} + +template<typename M> +static constexpr ArgumentType arg4Type() { + using Type = decltype(&M::call); + return Argument<4, Type>::type; +} + +template<typename M> +static constexpr ArgumentType arg5Type() { + using Type = decltype(&M::call); + return Argument<5, Type>::type; +} + +template<typename M> +static constexpr ArgumentType arg6Type() { + using Type = decltype(&M::call); + return Argument<6, Type>::type; +} + +} // namespace RuntimeSupport +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4RUNTIMESUPPORT_P_H 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 <QtCore/qloggingcategory.h> + +#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<unsigned>::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<Meta::Jump>(), ®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<Node *> NodeScheduler::buildCFG() +{ + std::vector<Node *> 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<Meta::Jump>(), &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<Node *> &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<Node *> &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<std::vector<SchedulerData *>> 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<unsigned>::max()) + vreg = m_nextVReg++; + return vreg; +} + +void NodeScheduler::addBlockStart(std::vector<Node *> &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<Node *, 32> 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("<<none>>"); + 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>(); } + + SchedulerData() = default; + ~SchedulerData() = default; + + Node *node = nullptr; + MIBlock *minimumBlock = nullptr; + bool isFixed = false; + bool isScheduledInBlock = false; + static constexpr unsigned NotYetCalculated = std::numeric_limits<unsigned>::max(); + unsigned unscheduledUses = NotYetCalculated; + }; + +public: + NodeScheduler(Function *irFunction); + ~NodeScheduler() = default; + + MIFunction *buildMIFunction(); + +private: + std::vector<Node *> buildCFG(); + void scheduleEarly(const std::vector<Node *> &roots); + void scheduleEarly(Node *node, NodeWorkList &todo); + void propagateMinimumPosition(MIBlock *newMinimumPosition, Node *toNode, NodeWorkList &todo); + void scheduleLate(const std::vector<Node *> &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<Node *> &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<LoopInfo> m_loopInfo; + QScopedPointer<DominatorTree> m_domTree; + std::vector<int> m_dominatorDepthForBlock; + std::vector<unsigned> m_vregs; + std::vector<SchedulerData *> 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 new file mode 100644 index 0000000000..c8974b3a1b --- /dev/null +++ b/src/qml/jit/qv4tracingjit.cpp @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** 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 <QtCore/qloggingcategory.h> + +#include "qv4vme_moth_p.h" +#include "qv4graphbuilder_p.h" +#include "qv4lowering_p.h" +#include "qv4mi_p.h" +#include "qv4schedulers_p.h" + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcTracing, "qt.v4.tracing") + +namespace QV4 { + +// This is the entry point for the "tracing JIT". It uses the sea-of-nodes concept as described in +// https://scholarship.rice.edu/bitstream/handle/1911/96451/TR95-252.pdf +// +// The minimal pipeline is as follows: +// - create the graph for the function +// - do generic lowering +// - schedule the nodes +// - run minimal stack slot allocation (no re-use of slots) +// - run the assembler +// +// This pipeline has no optimizations, and generates quite inefficient code. It does have the +// advantage that no trace information is used, so it can be used for testing where it replaces +// the baseline JIT. Any optimizations are additions to this pipeline. +// +// Note: generators (or resuming functions in general) are not supported by this JIT. +void Moth::runTracingJit(QV4::Function *function) +{ + IR::Function irFunction(function); + qCDebug(lcTracing).noquote() << "runTracingJit called for" << irFunction.name() << "..."; + + qCDebug(lcTracing).noquote().nospace() << function->traceInfoToString(); + + IR::GraphBuilder::buildGraph(&irFunction); + irFunction.dump(QStringLiteral("initial IR")); + irFunction.verify(); + + IR::GenericLowering(irFunction).lower(); + irFunction.dump(QStringLiteral("after generic lowering")); + irFunction.verify(); + + IR::NodeScheduler scheduler(&irFunction); + QScopedPointer<IR::MIFunction> miFunction(scheduler.buildMIFunction()); + miFunction->dump(QStringLiteral("initial MI")); + irFunction.verify(); +} + +} // QV4 namespace +QT_END_NAMESPACE diff --git a/src/qml/jsapi/qjsvalue.cpp b/src/qml/jsapi/qjsvalue.cpp index 1ab6e8c767..e0bd986920 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 <private/qv4mm_p.h> #include <private/qv4jscall_p.h> #include <private/qv4qobjectwrapper_p.h> @@ -1042,7 +1041,7 @@ bool QJSValue::equals(const QJSValue& other) const if (!ov) return other.equals(*this); - return Runtime::method_compareEqual(*v, *ov); + return Runtime::CompareEqual::call(*v, *ov); } /*! diff --git a/src/qml/jsruntime/qv4arrayobject.cpp b/src/qml/jsruntime/qv4arrayobject.cpp index b5b421fa39..b3e607d74a 100644 --- a/src/qml/jsruntime/qv4arrayobject.cpp +++ b/src/qml/jsruntime/qv4arrayobject.cpp @@ -219,7 +219,7 @@ ReturnedValue ArrayPrototype::method_from(const FunctionObject *builtin, const V // Item iteration supported, so let's go ahead and try use that. ScopedObject a(createObjectFromCtorOrArray(scope, thatCtor, false, 0)); CHECK_EXCEPTION(); - ScopedObject iterator(scope, Runtime::method_getIterator(scope.engine, itemsObject, true)); + ScopedObject iterator(scope, Runtime::GetIterator::call(scope.engine, itemsObject, true)); CHECK_EXCEPTION(); // symbol_iterator threw; whoops. if (!iterator) { return scope.engine->throwTypeError(); // symbol_iterator wasn't an object. @@ -236,11 +236,11 @@ ReturnedValue ArrayPrototype::method_from(const FunctionObject *builtin, const V if (k > (static_cast<qint64>(1) << 53) - 1) { ScopedValue falsey(scope, Encode(false)); ScopedValue error(scope, scope.engine->throwTypeError()); - return Runtime::method_iteratorClose(scope.engine, iterator, falsey); + return Runtime::IteratorClose::call(scope.engine, iterator, falsey); } // Retrieve the next value. If the iteration ends, we're done here. - done = Value::fromReturnedValue(Runtime::method_iteratorNext(scope.engine, iterator, nextValue)); + done = Value::fromReturnedValue(Runtime::IteratorNext::call(scope.engine, iterator, nextValue)); CHECK_EXCEPTION(); if (done->toBoolean()) { if (ArrayObject *ao = a->as<ArrayObject>()) { @@ -257,7 +257,7 @@ ReturnedValue ArrayPrototype::method_from(const FunctionObject *builtin, const V mapArguments[1] = Value::fromDouble(k); mappedValue = mapfn->call(thisArg, mapArguments, 2); if (scope.engine->hasException) - return Runtime::method_iteratorClose(scope.engine, iterator, Value::fromBoolean(false)); + return Runtime::IteratorClose::call(scope.engine, iterator, Value::fromBoolean(false)); } else { mappedValue = *nextValue; } @@ -271,7 +271,7 @@ ReturnedValue ArrayPrototype::method_from(const FunctionObject *builtin, const V if (scope.engine->hasException) { ScopedValue falsey(scope, Encode(false)); - return Runtime::method_iteratorClose(scope.engine, iterator, falsey); + return Runtime::IteratorClose::call(scope.engine, iterator, falsey); } k++; @@ -387,7 +387,7 @@ ReturnedValue ArrayPrototype::method_toLocaleString(const FunctionObject *b, con v = instance->get(k); if (v->isNullOrUndefined()) continue; - v = Runtime::method_callElement(scope.engine, v, *scope.engine->id_toLocaleString(), nullptr, 0); + v = Runtime::CallElement::call(scope.engine, v, *scope.engine->id_toLocaleString(), nullptr, 0); s = v->toString(scope.engine); if (scope.hasException()) return Encode::undefined(); diff --git a/src/qml/jsruntime/qv4context.cpp b/src/qml/jsruntime/qv4context.cpp index 94b1a9fb73..b3bcfe21d5 100644 --- a/src/qml/jsruntime/qv4context.cpp +++ b/src/qml/jsruntime/qv4context.cpp @@ -81,17 +81,17 @@ Heap::CallContext *ExecutionContext::newBlockContext(CppStackFrame *frame, int b return c; } -Heap::CallContext *ExecutionContext::cloneBlockContext(Heap::CallContext *context) +Heap::CallContext *ExecutionContext::cloneBlockContext(ExecutionEngine *engine, + Heap::CallContext *callContext) { - uint nLocals = context->locals.alloc; + uint nLocals = callContext->locals.alloc; size_t requiredMemory = sizeof(CallContext::Data) - sizeof(Value) + sizeof(Value) * nLocals; - ExecutionEngine *v4 = context->internalClass->engine; - Heap::CallContext *c = v4->memoryManager->allocManaged<CallContext>(requiredMemory, context->internalClass); - memcpy(c, context, requiredMemory); + Heap::CallContext *c = engine->memoryManager->allocManaged<CallContext>( + requiredMemory, callContext->internalClass); + memcpy(c, callContext, requiredMemory); return c; - } Heap::CallContext *ExecutionContext::newCallContext(CppStackFrame *frame) @@ -128,7 +128,7 @@ Heap::CallContext *ExecutionContext::newCallContext(CppStackFrame *frame) return c; } -Heap::ExecutionContext *ExecutionContext::newWithContext(Heap::Object *with) +Heap::ExecutionContext *ExecutionContext::newWithContext(Heap::Object *with) const { Heap::ExecutionContext *c = engine()->memoryManager->alloc<ExecutionContext>(Heap::ExecutionContext::Type_WithContext); c->outer.set(engine(), d()); diff --git a/src/qml/jsruntime/qv4context_p.h b/src/qml/jsruntime/qv4context_p.h index 5cd2f9ddf0..75fa2d08e6 100644 --- a/src/qml/jsruntime/qv4context_p.h +++ b/src/qml/jsruntime/qv4context_p.h @@ -150,9 +150,10 @@ struct Q_QML_EXPORT ExecutionContext : public Managed V4_INTERNALCLASS(ExecutionContext) static Heap::CallContext *newBlockContext(QV4::CppStackFrame *frame, int blockIndex); - static Heap::CallContext *cloneBlockContext(Heap::CallContext *context); + static Heap::CallContext *cloneBlockContext(ExecutionEngine *engine, + Heap::CallContext *callContext); static Heap::CallContext *newCallContext(QV4::CppStackFrame *frame); - Heap::ExecutionContext *newWithContext(Heap::Object *with); + Heap::ExecutionContext *newWithContext(Heap::Object *with) const; static Heap::ExecutionContext *newCatchContext(CppStackFrame *frame, int blockIndex, Heap::String *exceptionVarName); void createMutableBinding(String *name, bool deletable); diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index f506e4015e..ab980e99df 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -51,6 +51,9 @@ #include <QDir> #include <QFileInfo> #include <QLoggingCategory> +#if QT_CONFIG(regularexpression) +#include <QRegularExpression> +#endif #ifndef V4_BOOTSTRAP @@ -860,6 +863,13 @@ Heap::RegExpObject *ExecutionEngine::newRegExpObject(const QRegExp &re) return memoryManager->allocate<RegExpObject>(re); } +#if QT_CONFIG(regularexpression) +Heap::RegExpObject *ExecutionEngine::newRegExpObject(const QRegularExpression &re) +{ + return memoryManager->allocate<RegExpObject>(re); +} +#endif + Heap::Object *ExecutionEngine::newErrorObject(const Value &value) { return ErrorObject::create<ErrorObject>(this, value, errorCtor()); @@ -1072,6 +1082,12 @@ extern "C" Q_QML_EXPORT char *qt_v4StackTrace(void *executionContext) return v4StackTrace(reinterpret_cast<const ExecutionContext *>(executionContext)); } +extern "C" Q_QML_EXPORT char *qt_v4StackTraceForEngine(void *executionEngine) +{ + auto engine = (reinterpret_cast<const ExecutionEngine *>(executionEngine)); + return v4StackTrace(engine->currentContext()); +} + QUrl ExecutionEngine::resolvedUrl(const QString &file) { QUrl src(file); @@ -1269,6 +1285,7 @@ static bool convertToNativeQObject(QV4::ExecutionEngine *e, const QV4::Value &va const QByteArray &targetType, void **result); static QV4::ReturnedValue variantListToJS(QV4::ExecutionEngine *v4, const QVariantList &lst); +static QV4::ReturnedValue sequentialIterableToJS(QV4::ExecutionEngine *v4, const QSequentialIterable &lst); static QV4::ReturnedValue variantMapToJS(QV4::ExecutionEngine *v4, const QVariantMap &vmap); static QV4::ReturnedValue variantToJS(QV4::ExecutionEngine *v4, const QVariant &value) { @@ -1305,7 +1322,7 @@ static QVariant toVariant(QV4::ExecutionEngine *e, const QV4::Value &value, int && !value.as<ArrayObject>() && !value.as<FunctionObject>()) { return QVariant::fromValue(QV4::JsonObject::toJsonObject(object)); } else if (QV4::QObjectWrapper *wrapper = object->as<QV4::QObjectWrapper>()) { - return qVariantFromValue<QObject *>(wrapper->object()); + return QVariant::fromValue<QObject *>(wrapper->object()); } else if (object->as<QV4::QQmlContextWrapper>()) { return QVariant(); } else if (QV4::QQmlTypeWrapper *w = object->as<QV4::QQmlTypeWrapper>()) { @@ -1336,7 +1353,7 @@ static QVariant toVariant(QV4::ExecutionEngine *e, const QV4::Value &value, int } } - return qVariantFromValue<QList<QObject*> >(list); + return QVariant::fromValue<QList<QObject*> >(list); } else if (typeHint == QMetaType::QJsonArray) { return QVariant::fromValue(QV4::JsonObject::toJsonArray(a)); } @@ -1379,8 +1396,13 @@ static QVariant toVariant(QV4::ExecutionEngine *e, const QV4::Value &value, int QV4::ScopedObject o(scope, value); Q_ASSERT(o); - if (QV4::RegExpObject *re = o->as<QV4::RegExpObject>()) + if (QV4::RegExpObject *re = o->as<QV4::RegExpObject>()) { +#if QT_CONFIG(regularexpression) + if (typeHint != QMetaType::QRegExp) + return re->toQRegularExpression(); +#endif return re->toQRegExp(); + } if (createJSValueForObjects) return QVariant::fromValue(QJSValue(scope.engine, o->asReturnedValue())); @@ -1442,35 +1464,6 @@ static QVariant objectToVariant(QV4::ExecutionEngine *e, const QV4::Object *o, V return result; } -static QV4::ReturnedValue arrayFromVariantList(QV4::ExecutionEngine *e, const QVariantList &list) -{ - QV4::Scope scope(e); - QV4::ScopedArrayObject a(scope, e->newArrayObject()); - int len = list.count(); - a->arrayReserve(len); - QV4::ScopedValue v(scope); - for (int ii = 0; ii < len; ++ii) - a->arrayPut(ii, (v = scope.engine->fromVariant(list.at(ii)))); - - a->setArrayLengthUnchecked(len); - return a.asReturnedValue(); -} - -static QV4::ReturnedValue objectFromVariantMap(QV4::ExecutionEngine *e, const QVariantMap &map) -{ - QV4::Scope scope(e); - QV4::ScopedObject o(scope, e->newObject()); - QV4::ScopedString s(scope); - QV4::ScopedValue v(scope); - for (QVariantMap::const_iterator iter = map.begin(), cend = map.end(); iter != cend; ++iter) { - s = e->newString(iter.key()); - o->put(s, (v = e->fromVariant(iter.value()))); - } - return o.asReturnedValue(); -} - -Q_CORE_EXPORT QString qt_regexp_toCanonical(const QString &, QRegExp::PatternSyntax); - QV4::ReturnedValue QV4::ExecutionEngine::fromVariant(const QVariant &variant) { int type = variant.userType(); @@ -1520,6 +1513,10 @@ QV4::ReturnedValue QV4::ExecutionEngine::fromVariant(const QVariant &variant) return QV4::Encode(newDateObjectFromTime(*reinterpret_cast<const QTime *>(ptr))); case QMetaType::QRegExp: return QV4::Encode(newRegExpObject(*reinterpret_cast<const QRegExp *>(ptr))); +#if QT_CONFIG(regularexpression) + case QMetaType::QRegularExpression: + return QV4::Encode(newRegExpObject(*reinterpret_cast<const QRegularExpression *>(ptr))); +#endif case QMetaType::QObjectStar: return QV4::QObjectWrapper::wrap(this, *reinterpret_cast<QObject* const *>(ptr)); #if QT_CONFIG(qml_sequence_object) @@ -1534,9 +1531,9 @@ QV4::ReturnedValue QV4::ExecutionEngine::fromVariant(const QVariant &variant) } #endif case QMetaType::QVariantList: - return arrayFromVariantList(this, *reinterpret_cast<const QVariantList *>(ptr)); + return variantListToJS(this, *reinterpret_cast<const QVariantList *>(ptr)); case QMetaType::QVariantMap: - return objectFromVariantMap(this, *reinterpret_cast<const QVariantMap *>(ptr)); + return variantMapToJS(this, *reinterpret_cast<const QVariantMap *>(ptr)); case QMetaType::QJsonValue: return QV4::JsonObject::fromJsonValue(this, *reinterpret_cast<const QJsonValue *>(ptr)); case QMetaType::QJsonObject: @@ -1593,6 +1590,11 @@ QV4::ReturnedValue QV4::ExecutionEngine::fromVariant(const QVariant &variant) return retn->asReturnedValue(); #endif + if (QMetaType::hasRegisteredConverterFunction(type, qMetaTypeId<QtMetaTypePrivate::QSequentialIterableImpl>())) { + QSequentialIterable lst = variant.value<QSequentialIterable>(); + return sequentialIterableToJS(this, lst); + } + if (const QMetaObject *vtmo = QQmlValueTypeFactory::metaObjectForMetaType(type)) return QV4::QQmlValueTypeWrapper::create(this, variant, vtmo, type); } @@ -1672,99 +1674,13 @@ QV4::ReturnedValue ExecutionEngine::metaTypeToJS(int type, const void *data) { Q_ASSERT(data != nullptr); - // check if it's one of the types we know - switch (QMetaType::Type(type)) { - case QMetaType::UnknownType: - case QMetaType::Void: - return QV4::Encode::undefined(); - case QMetaType::Nullptr: - case QMetaType::VoidStar: - return QV4::Encode::null(); - case QMetaType::Bool: - return QV4::Encode(*reinterpret_cast<const bool*>(data)); - case QMetaType::Int: - return QV4::Encode(*reinterpret_cast<const int*>(data)); - case QMetaType::UInt: - return QV4::Encode(*reinterpret_cast<const uint*>(data)); - case QMetaType::LongLong: - return QV4::Encode(double(*reinterpret_cast<const qlonglong*>(data))); - case QMetaType::ULongLong: -#if defined(Q_OS_WIN) && defined(_MSC_FULL_VER) && _MSC_FULL_VER <= 12008804 -#pragma message("** NOTE: You need the Visual Studio Processor Pack to compile support for 64bit unsigned integers.") - return QV4::Encode(double((qlonglong)*reinterpret_cast<const qulonglong*>(data))); -#elif defined(Q_CC_MSVC) && !defined(Q_CC_MSVC_NET) - return QV4::Encode(double((qlonglong)*reinterpret_cast<const qulonglong*>(data))); -#else - return QV4::Encode(double(*reinterpret_cast<const qulonglong*>(data))); -#endif - case QMetaType::Double: - return QV4::Encode(*reinterpret_cast<const double*>(data)); - case QMetaType::QString: - return newString(*reinterpret_cast<const QString*>(data))->asReturnedValue(); - case QMetaType::QByteArray: - return newArrayBuffer(*reinterpret_cast<const QByteArray*>(data))->asReturnedValue(); - case QMetaType::Float: - return QV4::Encode(*reinterpret_cast<const float*>(data)); - case QMetaType::Short: - return QV4::Encode((int)*reinterpret_cast<const short*>(data)); - case QMetaType::UShort: - return QV4::Encode((int)*reinterpret_cast<const unsigned short*>(data)); - case QMetaType::Char: - return QV4::Encode((int)*reinterpret_cast<const char*>(data)); - case QMetaType::UChar: - return QV4::Encode((int)*reinterpret_cast<const unsigned char*>(data)); - case QMetaType::QChar: - return QV4::Encode((int)(*reinterpret_cast<const QChar*>(data)).unicode()); - case QMetaType::QStringList: - return QV4::Encode(newArrayObject(*reinterpret_cast<const QStringList *>(data))); - case QMetaType::QVariantList: - return variantListToJS(this, *reinterpret_cast<const QVariantList *>(data)); - case QMetaType::QVariantMap: - return variantMapToJS(this, *reinterpret_cast<const QVariantMap *>(data)); - case QMetaType::QDateTime: - return QV4::Encode(newDateObject(*reinterpret_cast<const QDateTime *>(data))); - case QMetaType::QDate: - return QV4::Encode(newDateObject(QDateTime(*reinterpret_cast<const QDate *>(data)))); - case QMetaType::QRegExp: - return QV4::Encode(newRegExpObject(*reinterpret_cast<const QRegExp *>(data))); - case QMetaType::QObjectStar: - return QV4::QObjectWrapper::wrap(this, *reinterpret_cast<QObject* const *>(data)); - case QMetaType::QVariant: + QVariant variant(type, data); + if (QMetaType::Type(variant.type()) == QMetaType::QVariant) { + // unwrap it: this is tested in QJSEngine, and makes the most sense for + // end-user code too. return variantToJS(this, *reinterpret_cast<const QVariant*>(data)); - case QMetaType::QJsonValue: - return QV4::JsonObject::fromJsonValue(this, *reinterpret_cast<const QJsonValue *>(data)); - case QMetaType::QJsonObject: - return QV4::JsonObject::fromJsonObject(this, *reinterpret_cast<const QJsonObject *>(data)); - case QMetaType::QJsonArray: - return QV4::JsonObject::fromJsonArray(this, *reinterpret_cast<const QJsonArray *>(data)); - default: - if (type == qMetaTypeId<QJSValue>()) { - return QJSValuePrivate::convertedToValue(this, *reinterpret_cast<const QJSValue*>(data)); - } else { - QByteArray typeName = QMetaType::typeName(type); - if (typeName.endsWith('*') && !*reinterpret_cast<void* const *>(data)) { - return QV4::Encode::null(); - } - QMetaType mt(type); - if (auto metaObject = mt.metaObject()) { - auto flags = mt.flags(); - if (flags & QMetaType::IsGadget) { - return QV4::QQmlValueTypeWrapper::create(this, QVariant(type, data), metaObject, type); - } else if (flags & QMetaType::PointerToQObject) { - return QV4::QObjectWrapper::wrap(this, *reinterpret_cast<QObject* const *>(data)); - } - } - if (QMetaType::hasRegisteredConverterFunction(type, qMetaTypeId<QtMetaTypePrivate::QSequentialIterableImpl>())) { - auto v = QVariant(type, data); - QSequentialIterable lst = v.value<QSequentialIterable>(); - return sequentialIterableToJS(this, lst); - } - // Fall back to wrapping in a QVariant. - return QV4::Encode(newVariantObject(QVariant(type, data))); - } } - Q_UNREACHABLE(); - return 0; + return fromVariant(variant); } ReturnedValue ExecutionEngine::global() @@ -1969,6 +1885,13 @@ bool ExecutionEngine::metaTypeFromJS(const Value *value, int type, void *data) *reinterpret_cast<QRegExp *>(data) = r->toQRegExp(); return true; } break; +#if QT_CONFIG(regularexpression) + case QMetaType::QRegularExpression: + if (const QV4::RegExpObject *r = value->as<QV4::RegExpObject>()) { + *reinterpret_cast<QRegularExpression *>(data) = r->toQRegularExpression(); + return true; + } break; +#endif case QMetaType::QObjectStar: { const QV4::QObjectWrapper *qobjectWrapper = value->as<QV4::QObjectWrapper>(); if (qobjectWrapper || value->isNull()) { diff --git a/src/qml/jsruntime/qv4engine_p.h b/src/qml/jsruntime/qv4engine_p.h index 86367c0ece..6df4545014 100644 --- a/src/qml/jsruntime/qv4engine_p.h +++ b/src/qml/jsruntime/qv4engine_p.h @@ -520,6 +520,9 @@ public: Heap::RegExpObject *newRegExpObject(const QString &pattern, int flags); Heap::RegExpObject *newRegExpObject(RegExp *re); Heap::RegExpObject *newRegExpObject(const QRegExp &re); +#if QT_CONFIG(regularexpression) + Heap::RegExpObject *newRegExpObject(const QRegularExpression &re); +#endif Heap::Object *newErrorObject(const Value &value); Heap::Object *newErrorObject(const QString &message); @@ -631,17 +634,6 @@ private: QScopedPointer<QV4::Promise::ReactionHandler> m_reactionHandler; }; -// This is a trick to tell the code generators that functions taking a NoThrowContext won't -// throw exceptions and therefore don't need a check after the call. -#ifndef V4_BOOTSTRAP -struct NoThrowEngine : public ExecutionEngine -{ -}; -#else -struct NoThrowEngine; -#endif - - #define CHECK_STACK_LIMITS(v4) if ((v4)->checkStackLimits()) return Encode::undefined(); \ ExecutionEngineCallDepthRecorder _executionEngineCallDepthRecorder(v4); diff --git a/src/qml/jsruntime/qv4lookup.cpp b/src/qml/jsruntime/qv4lookup.cpp index 54bce7d7b3..c2c3fa0474 100644 --- a/src/qml/jsruntime/qv4lookup.cpp +++ b/src/qml/jsruntime/qv4lookup.cpp @@ -473,11 +473,11 @@ bool Lookup::setterTwoClasses(Lookup *l, ExecutionEngine *engine, Value &object, return false; } - if (l->setter == Lookup::setter0 || l->setter == Lookup::setter0Inline) { + if (l->setter == Lookup::setter0MemberData || l->setter == Lookup::setter0Inline) { l->objectLookupTwoClasses.ic = first.objectLookup.ic; l->objectLookupTwoClasses.ic2 = second.objectLookup.ic; - l->objectLookupTwoClasses.offset = first.objectLookup.offset; - l->objectLookupTwoClasses.offset2 = second.objectLookup.offset; + l->objectLookupTwoClasses.offset = first.objectLookup.index; + l->objectLookupTwoClasses.offset2 = second.objectLookup.index; l->setter = setter0setter0; return true; } @@ -498,11 +498,11 @@ bool Lookup::setterFallback(Lookup *l, ExecutionEngine *engine, Value &object, c return o->put(name, value); } -bool Lookup::setter0(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value) +bool Lookup::setter0MemberData(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value) { Heap::Object *o = static_cast<Heap::Object *>(object.heapObject()); if (o && o->internalClass == l->objectLookup.ic) { - o->setProperty(engine, l->objectLookup.offset, value); + o->memberData->values.set(engine, l->objectLookup.offset, value); return true; } @@ -513,7 +513,7 @@ bool Lookup::setter0Inline(Lookup *l, ExecutionEngine *engine, Value &object, co { Heap::Object *o = static_cast<Heap::Object *>(object.heapObject()); if (o && o->internalClass == l->objectLookup.ic) { - o->setInlineProperty(engine, l->objectLookup.offset, value); + o->setInlinePropertyWithOffset(engine, l->objectLookup.offset, value); return true; } diff --git a/src/qml/jsruntime/qv4lookup_p.h b/src/qml/jsruntime/qv4lookup_p.h index 03dc5f6d3c..7309749a81 100644 --- a/src/qml/jsruntime/qv4lookup_p.h +++ b/src/qml/jsruntime/qv4lookup_p.h @@ -81,8 +81,9 @@ struct Lookup { } markDef; struct { Heap::InternalClass *ic; - quintptr _unused; - int offset; + quintptr unused; + uint index; + uint offset; } objectLookup; struct { quintptr protoId; @@ -92,8 +93,8 @@ struct Lookup { struct { Heap::InternalClass *ic; Heap::InternalClass *ic2; - int offset; - int offset2; + uint offset; + uint offset2; } objectLookupTwoClasses; struct { quintptr protoId; @@ -111,12 +112,14 @@ struct Lookup { struct { Heap::InternalClass *newClass; quintptr protoId; - int offset; + uint offset; + uint unused; } insertionLookup; struct { quintptr _unused; quintptr _unused2; uint index; + uint unused; } indexedLookup; struct { Heap::InternalClass *ic; @@ -187,7 +190,7 @@ struct Lookup { static bool setterGeneric(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); Q_NEVER_INLINE static bool setterTwoClasses(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); static bool setterFallback(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); - static bool setter0(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); + static bool setter0MemberData(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); static bool setter0Inline(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); static bool setter0setter0(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); static bool setterInsert(Lookup *l, ExecutionEngine *engine, Value &object, const Value &value); diff --git a/src/qml/jsruntime/qv4mapobject.cpp b/src/qml/jsruntime/qv4mapobject.cpp index 68741e7677..90e1908a84 100644 --- a/src/qml/jsruntime/qv4mapobject.cpp +++ b/src/qml/jsruntime/qv4mapobject.cpp @@ -80,7 +80,7 @@ ReturnedValue WeakMapCtor::construct(const FunctionObject *f, const Value *argv, if (!adder) return scope.engine->throwTypeError(); - ScopedObject iter(scope, Runtime::method_getIterator(scope.engine, iterable, true)); + ScopedObject iter(scope, Runtime::GetIterator::call(scope.engine, iterable, true)); if (scope.hasException()) return Encode::undefined(); Q_ASSERT(iter); @@ -89,7 +89,7 @@ ReturnedValue WeakMapCtor::construct(const FunctionObject *f, const Value *argv, Value *arguments = scope.alloc(2); ScopedValue done(scope); forever { - done = Runtime::method_iteratorNext(scope.engine, iter, obj); + done = Runtime::IteratorNext::call(scope.engine, iter, obj); if (scope.hasException()) break; if (done->toBoolean()) @@ -112,7 +112,7 @@ ReturnedValue WeakMapCtor::construct(const FunctionObject *f, const Value *argv, break; } ScopedValue falsey(scope, Encode(false)); - return Runtime::method_iteratorClose(scope.engine, iter, falsey); + return Runtime::IteratorClose::call(scope.engine, iter, falsey); } } return a->asReturnedValue(); diff --git a/src/qml/jsruntime/qv4object.cpp b/src/qml/jsruntime/qv4object.cpp index 7dd0a247d6..efab9a6454 100644 --- a/src/qml/jsruntime/qv4object.cpp +++ b/src/qml/jsruntime/qv4object.cpp @@ -786,8 +786,15 @@ bool Object::virtualResolveLookupSetter(Object *object, ExecutionEngine *engine, 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; + lookup->objectLookup.index = idx.index; + const auto nInline = object->d()->vtable()->nInlineProperties; + if (idx.index < nInline) { + lookup->setter = Lookup::setter0Inline; + lookup->objectLookup.offset = idx.index + object->d()->vtable()->inlinePropertyOffset; + } else { + lookup->setter = Lookup::setter0MemberData; + lookup->objectLookup.offset = idx.index - nInline; + } return lookup->setter(lookup, engine, *object, value); } else { // ### handle setter diff --git a/src/qml/jsruntime/qv4object_p.h b/src/qml/jsruntime/qv4object_p.h index c3f1cb2c35..567382cbc0 100644 --- a/src/qml/jsruntime/qv4object_p.h +++ b/src/qml/jsruntime/qv4object_p.h @@ -79,21 +79,21 @@ DECLARE_EXPORTED_HEAP_OBJECT(Object, Base) { } const Value *inlinePropertyDataWithOffset(uint indexWithOffset) const { - Q_ASSERT(indexWithOffset >= vtable()->inlinePropertyOffset && indexWithOffset < vtable()->inlinePropertyOffset + vtable()->nInlineProperties); + Q_ASSERT(indexWithOffset >= vtable()->inlinePropertyOffset && indexWithOffset < uint(vtable()->inlinePropertyOffset + vtable()->nInlineProperties)); return reinterpret_cast<const Value *>(this) + indexWithOffset; } const Value *inlinePropertyData(uint index) const { Q_ASSERT(index < vtable()->nInlineProperties); return reinterpret_cast<const Value *>(this) + vtable()->inlinePropertyOffset + index; } - void setInlineProperty(ExecutionEngine *e, uint index, Value v) { - Q_ASSERT(index < vtable()->nInlineProperties); - Value *prop = reinterpret_cast<Value *>(this) + vtable()->inlinePropertyOffset + index; + void setInlinePropertyWithOffset(ExecutionEngine *e, uint indexWithOffset, Value v) { + Q_ASSERT(indexWithOffset >= vtable()->inlinePropertyOffset && indexWithOffset < uint(vtable()->inlinePropertyOffset + vtable()->nInlineProperties)); + Value *prop = reinterpret_cast<Value *>(this) + indexWithOffset; WriteBarrier::write(e, this, prop->data_ptr(), v.asReturnedValue()); } - void setInlineProperty(ExecutionEngine *e, uint index, Heap::Base *b) { - Q_ASSERT(index < vtable()->nInlineProperties); - Value *prop = reinterpret_cast<Value *>(this) + vtable()->inlinePropertyOffset + index; + void setInlinePropertyWithOffset(ExecutionEngine *e, uint indexWithOffset, Heap::Base *b) { + Q_ASSERT(indexWithOffset >= vtable()->inlinePropertyOffset && indexWithOffset < uint(vtable()->inlinePropertyOffset + vtable()->nInlineProperties)); + Value *prop = reinterpret_cast<Value *>(this) + indexWithOffset; WriteBarrier::write(e, this, prop->data_ptr(), Value::fromHeapObject(b).asReturnedValue()); } @@ -115,7 +115,7 @@ DECLARE_EXPORTED_HEAP_OBJECT(Object, Base) { void setProperty(ExecutionEngine *e, uint index, Value v) { uint nInline = vtable()->nInlineProperties; if (index < nInline) { - setInlineProperty(e, index, v); + setInlinePropertyWithOffset(e, index + vtable()->inlinePropertyOffset, v); return; } index -= nInline; @@ -124,7 +124,7 @@ DECLARE_EXPORTED_HEAP_OBJECT(Object, Base) { void setProperty(ExecutionEngine *e, uint index, Heap::Base *b) { uint nInline = vtable()->nInlineProperties; if (index < nInline) { - setInlineProperty(e, index, b); + setInlinePropertyWithOffset(e, index + vtable()->inlinePropertyOffset, b); return; } index -= nInline; diff --git a/src/qml/jsruntime/qv4promiseobject.cpp b/src/qml/jsruntime/qv4promiseobject.cpp index 8450655334..b32e227b58 100644 --- a/src/qml/jsruntime/qv4promiseobject.cpp +++ b/src/qml/jsruntime/qv4promiseobject.cpp @@ -496,7 +496,7 @@ ReturnedValue PromiseCtor::method_all(const FunctionObject *f, const Value *this ScopedFunctionObject reject(scope, capability->d()->reject); ScopedObject itemsObject(scope, argv); - ScopedObject iteratorObject(scope, Runtime::method_getIterator(e, itemsObject, true)); + ScopedObject iteratorObject(scope, Runtime::GetIterator::call(e, itemsObject, true)); if (!iteratorObject || scope.hasException()) { ScopedObject error(scope); if (scope.hasException()) { @@ -521,7 +521,7 @@ ReturnedValue PromiseCtor::method_all(const FunctionObject *f, const Value *this for (;;) { Scope scope(e); ScopedValue nextValue(scope); - doneValue = Value::fromReturnedValue(Runtime::method_iteratorNext(e, iteratorObject, nextValue)); + doneValue = Value::fromReturnedValue(Runtime::IteratorNext::call(e, iteratorObject, nextValue)); if (doneValue->toBoolean()) break; @@ -549,7 +549,7 @@ ReturnedValue PromiseCtor::method_all(const FunctionObject *f, const Value *this } if (!doneValue->toBoolean()) - completion = Runtime::method_iteratorClose(e, iteratorObject, doneValue); + completion = Runtime::IteratorClose::call(e, iteratorObject, doneValue); reject->call(newPromise, completion, 1); return newPromise.asReturnedValue(); @@ -557,7 +557,7 @@ ReturnedValue PromiseCtor::method_all(const FunctionObject *f, const Value *this ScopedObject nextPromise(scope, Value::fromReturnedValue(resolve->call(thisObject, nextValue, 1))); if (!nextPromise || scope.hasException()) { - ScopedValue completion(scope, Runtime::method_iteratorClose(e, iteratorObject, doneValue)); + ScopedValue completion(scope, Runtime::IteratorClose::call(e, iteratorObject, doneValue)); if (scope.hasException()) { completion = e->exceptionValue->asReturnedValue(); dropException(e); @@ -579,7 +579,7 @@ ReturnedValue PromiseCtor::method_all(const FunctionObject *f, const Value *this } if (!doneValue->toBoolean()) - completion = Runtime::method_iteratorClose(scope.engine, iteratorObject, doneValue); + completion = Runtime::IteratorClose::call(scope.engine, iteratorObject, doneValue); reject->call(newPromise, completion, 1); return newPromise.asReturnedValue(); @@ -598,7 +598,7 @@ ReturnedValue PromiseCtor::method_all(const FunctionObject *f, const Value *this dropException(e); if (!doneValue->toBoolean()) - completion = Runtime::method_iteratorClose(scope.engine, iteratorObject, doneValue); + completion = Runtime::IteratorClose::call(scope.engine, iteratorObject, doneValue); reject->call(newPromise, completion, 1); return newPromise.asReturnedValue(); @@ -646,7 +646,7 @@ ReturnedValue PromiseCtor::method_race(const FunctionObject *f, const Value *thi ScopedFunctionObject reject(scope, capability->d()->reject); ScopedObject itemsObject(scope, argv); - ScopedObject iteratorObject(scope, Runtime::method_getIterator(e, itemsObject, true)); + ScopedObject iteratorObject(scope, Runtime::GetIterator::call(e, itemsObject, true)); if (!iteratorObject) { ScopedObject error(scope, e->newTypeErrorObject(QStringLiteral("Type error"))); reject->call(newPromise, error, 1); @@ -657,10 +657,10 @@ ReturnedValue PromiseCtor::method_race(const FunctionObject *f, const Value *thi for (;;) { Scope scope(e); ScopedValue nextValue(scope); - doneValue = Value::fromReturnedValue(Runtime::method_iteratorNext(e, iteratorObject, nextValue)); + doneValue = Value::fromReturnedValue(Runtime::IteratorNext::call(e, iteratorObject, nextValue)); if (scope.hasException()) { - ScopedValue completion(scope, Runtime::method_iteratorClose(e, iteratorObject, doneValue)); + ScopedValue completion(scope, Runtime::IteratorClose::call(e, iteratorObject, doneValue)); if (scope.hasException()) { completion = e->exceptionValue->asReturnedValue(); dropException(e); @@ -695,7 +695,7 @@ ReturnedValue PromiseCtor::method_race(const FunctionObject *f, const Value *thi } if (!doneValue->toBoolean()) - completion = Runtime::method_iteratorClose(e, iteratorObject, doneValue); + completion = Runtime::IteratorClose::call(e, iteratorObject, doneValue); reject->call(newPromise, completion, 1); return newPromise.asReturnedValue(); @@ -703,7 +703,7 @@ ReturnedValue PromiseCtor::method_race(const FunctionObject *f, const Value *thi ScopedObject nextPromise(scope, Value::fromReturnedValue(resolve->call(thisObject, nextValue, 1))); if (!nextPromise || scope.hasException()) { - ScopedValue completion(scope, Runtime::method_iteratorClose(e, iteratorObject, doneValue)); + ScopedValue completion(scope, Runtime::IteratorClose::call(e, iteratorObject, doneValue)); if (scope.hasException()) { completion = e->exceptionValue->asReturnedValue(); dropException(e); @@ -723,7 +723,7 @@ ReturnedValue PromiseCtor::method_race(const FunctionObject *f, const Value *thi } if (!doneValue->toBoolean()) - completion = Runtime::method_iteratorClose(e, iteratorObject, doneValue); + completion = Runtime::IteratorClose::call(e, iteratorObject, doneValue); reject->call(newPromise, completion, 1); return newPromise.asReturnedValue(); @@ -742,7 +742,7 @@ ReturnedValue PromiseCtor::method_race(const FunctionObject *f, const Value *thi dropException(e); if (!doneValue->toBoolean()) - completion = Runtime::method_iteratorClose(e, iteratorObject, doneValue); + completion = Runtime::IteratorClose::call(e, iteratorObject, doneValue); reject->call(newPromise, completion, 1); return newPromise.asReturnedValue(); 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 <private/qv8engine_p.h> #include <private/qqmlengine_p.h> #include <private/qqmlcontext_p.h> diff --git a/src/qml/jsruntime/qv4qobjectwrapper.cpp b/src/qml/jsruntime/qv4qobjectwrapper.cpp index 377c30617a..d85e8c94eb 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper.cpp +++ b/src/qml/jsruntime/qv4qobjectwrapper.cpp @@ -39,7 +39,7 @@ #include "qv4qobjectwrapper_p.h" -#include <private/qqmlpropertycache_p.h> +#include <private/qqmlstaticmetaobject_p.h> #include <private/qqmlengine_p.h> #include <private/qqmlvmemetaobject_p.h> #include <private/qqmlbinding_p.h> @@ -1384,6 +1384,9 @@ static int MatchScore(const QV4::Value &actual, int conversionType) } else if (actual.as<QV4::RegExpObject>()) { switch (conversionType) { case QMetaType::QRegExp: +#if QT_CONFIG(regularexpression) + case QMetaType::QRegularExpression: +#endif return 0; default: return 10; diff --git a/src/qml/jsruntime/qv4qobjectwrapper_p.h b/src/qml/jsruntime/qv4qobjectwrapper_p.h index 2558ede401..795bf241f2 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper_p.h +++ b/src/qml/jsruntime/qv4qobjectwrapper_p.h @@ -55,9 +55,7 @@ #include <QtCore/qmetatype.h> #include <QtCore/qpair.h> #include <QtCore/qhash.h> -#include <private/qhashedstring_p.h> #include <private/qqmldata_p.h> -#include <private/qqmlpropertycache_p.h> #include <private/qintrusivelist_p.h> #include <private/qv4value_p.h> diff --git a/src/qml/jsruntime/qv4reflect.cpp b/src/qml/jsruntime/qv4reflect.cpp index 15dcb602eb..0772770d63 100644 --- a/src/qml/jsruntime/qv4reflect.cpp +++ b/src/qml/jsruntime/qv4reflect.cpp @@ -148,7 +148,7 @@ ReturnedValue Reflect::method_deleteProperty(const FunctionObject *f, const Valu if (!argc || !argv[0].isObject()) return e->throwTypeError(); - bool result = Runtime::method_deleteProperty(e, argv[0], argc > 1 ? argv[1] : Value::undefinedValue()); + bool result = Runtime::DeleteProperty_NoThrow::call(e, argv[0], argc > 1 ? argv[1] : Value::undefinedValue()); return Encode(result); } diff --git a/src/qml/jsruntime/qv4regexpobject.cpp b/src/qml/jsruntime/qv4regexpobject.cpp index 39a2e96b45..5bd25dcbec 100644 --- a/src/qml/jsruntime/qv4regexpobject.cpp +++ b/src/qml/jsruntime/qv4regexpobject.cpp @@ -50,6 +50,9 @@ #include <QtCore/QDebug> #include <QtCore/qregexp.h> +#if QT_CONFIG(regularexpression) +#include <QtCore/qregularexpression.h> +#endif #include <cassert> #include <typeinfo> #include <iostream> @@ -134,6 +137,25 @@ void Heap::RegExpObject::init(const QRegExp &re) o->initProperties(); } +#if QT_CONFIG(regularexpression) +// Converts a QRegularExpression to a JS RegExp. +// The conversion is not 100% exact since ECMA regexp and QRegularExpression +// have different semantics/flags, but we try to do our best. +void Heap::RegExpObject::init(const QRegularExpression &re) +{ + Object::init(); + + Scope scope(internalClass->engine); + Scoped<QV4::RegExpObject> o(scope, this); + + const uint flags = (re.patternOptions() & QRegularExpression::CaseInsensitiveOption) + ? CompiledData::RegExp::RegExp_IgnoreCase + : CompiledData::RegExp::RegExp_NoFlags; + o->d()->value.set(scope.engine, QV4::RegExp::create(scope.engine, re.pattern(), flags)); + o->initProperties(); +} +#endif + void RegExpObject::initProperties() { setProperty(Index_LastIndex, Value::fromInt32(0)); @@ -150,6 +172,20 @@ QRegExp RegExpObject::toQRegExp() const return QRegExp(*value()->pattern, caseSensitivity, QRegExp::RegExp2); } +#if QT_CONFIG(regularexpression) +// Converts a JS RegExp to a QRegularExpression. +// The conversion is not 100% exact since ECMA regexp and QRegularExpression +// have different semantics/flags, but we try to do our best. +QRegularExpression RegExpObject::toQRegularExpression() const +{ + QRegularExpression::PatternOptions caseSensitivity + = (value()->flags & CompiledData::RegExp::RegExp_IgnoreCase) + ? QRegularExpression::CaseInsensitiveOption + : QRegularExpression::NoPatternOption; + return QRegularExpression(*value()->pattern, caseSensitivity); +} +#endif + QString RegExpObject::toString() const { QString p = *value()->pattern; diff --git a/src/qml/jsruntime/qv4regexpobject_p.h b/src/qml/jsruntime/qv4regexpobject_p.h index a584404c0b..04b533e49d 100644 --- a/src/qml/jsruntime/qv4regexpobject_p.h +++ b/src/qml/jsruntime/qv4regexpobject_p.h @@ -81,6 +81,9 @@ DECLARE_HEAP_OBJECT(RegExpObject, Object) { void init(); void init(QV4::RegExp *value); void init(const QRegExp &re); +#if QT_CONFIG(regularexpression) + void init(const QRegularExpression &re); +#endif }; #define RegExpCtorMembers(class, Member) \ @@ -138,6 +141,9 @@ struct RegExpObject: Object { } QRegExp toQRegExp() const; +#if QT_CONFIG(regularexpression) + QRegularExpression toQRegularExpression() const; +#endif QString toString() const; QString source() const; diff --git a/src/qml/jsruntime/qv4runtime.cpp b/src/qml/jsruntime/qv4runtime.cpp index f7c339dc26..8f2b162106 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 <private/qv8engine_p.h> #endif #include <QtCore/QDebug> @@ -225,13 +224,6 @@ void RuntimeCounters::count(const char *func, uint tag1, uint tag2) #ifndef V4_BOOTSTRAP -Runtime::Runtime() -{ -#define INIT_METHOD(returnvalue, name, args) runtimeMethods[name] = reinterpret_cast<void*>(&method_##name); -FOR_EACH_RUNTIME_METHOD(INIT_METHOD) -#undef INIT_METHOD -} - void RuntimeHelpers::numberToString(QString *result, double num, int radix) { Q_ASSERT(result); @@ -320,7 +312,7 @@ void RuntimeHelpers::numberToString(QString *result, double num, int radix) result->prepend(QLatin1Char('-')); } -ReturnedValue Runtime::method_closure(ExecutionEngine *engine, int functionId) +ReturnedValue Runtime::Closure::call(ExecutionEngine *engine, int functionId) { QV4::Function *clos = static_cast<CompiledData::CompilationUnit*>(engine->currentStackFrame->v4Function->compilationUnit)->runtimeFunctions[functionId]; Q_ASSERT(clos); @@ -330,7 +322,7 @@ ReturnedValue Runtime::method_closure(ExecutionEngine *engine, int functionId) return FunctionObject::createScriptFunction(current, clos)->asReturnedValue(); } -bool Runtime::method_deleteProperty(ExecutionEngine *engine, const Value &base, const Value &index) +Bool Runtime::DeleteProperty_NoThrow::call(ExecutionEngine *engine, const Value &base, const Value &index) { Scope scope(engine); ScopedObject o(scope, base.toObject(engine)); @@ -344,14 +336,36 @@ bool Runtime::method_deleteProperty(ExecutionEngine *engine, const Value &base, return o->deleteProperty(key); } -bool Runtime::method_deleteName(ExecutionEngine *engine, int nameIndex) +ReturnedValue Runtime::DeleteProperty::call(ExecutionEngine *engine, QV4::Function *function, const QV4::Value &base, const QV4::Value &index) +{ + if (!Runtime::DeleteProperty_NoThrow::call(engine, base, index)) { + if (function->isStrict()) + engine->throwTypeError(); + return Encode(false); + } else { + return Encode(true); + } +} + +Bool Runtime::DeleteName_NoThrow::call(ExecutionEngine *engine, int nameIndex) { Scope scope(engine); ScopedString name(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]); return static_cast<ExecutionContext &>(engine->currentStackFrame->jsFrame->context).deleteProperty(name); } -QV4::ReturnedValue Runtime::method_instanceof(ExecutionEngine *engine, const Value &lval, const Value &rval) +ReturnedValue Runtime::DeleteName::call(ExecutionEngine *engine, Function *function, int name) +{ + if (!Runtime::DeleteName_NoThrow::call(engine, name)) { + if (function->isStrict()) + engine->throwTypeError(); + return Encode(false); + } else { + return Encode(true); + } +} + +QV4::ReturnedValue Runtime::Instanceof::call(ExecutionEngine *engine, const Value &lval, const Value &rval) { // 11.8.6, 5: rval must be an Object const Object *rhs = rval.as<Object>(); @@ -376,7 +390,7 @@ QV4::ReturnedValue Runtime::method_instanceof(ExecutionEngine *engine, const Val return Encode(result->toBoolean()); } -QV4::ReturnedValue Runtime::method_in(ExecutionEngine *engine, const Value &left, const Value &right) +QV4::ReturnedValue Runtime::In::call(ExecutionEngine *engine, const Value &left, const Value &right) { Object *ro = right.objectValue(); if (!ro) @@ -604,13 +618,12 @@ QV4::ReturnedValue RuntimeHelpers::addHelper(ExecutionEngine *engine, const Valu return Encode(x + y); } -ReturnedValue RuntimeHelpers::getTemplateObject(Function *function, int index) +ReturnedValue Runtime::GetTemplateObject::call(Function *function, int index) { return function->compilationUnit->templateObjectAt(index)->asReturnedValue(); } - -void Runtime::method_storeProperty(ExecutionEngine *engine, const Value &object, int nameIndex, const Value &value) +void Runtime::StoreProperty::call(ExecutionEngine *engine, const Value &object, int nameIndex, const Value &value) { Scope scope(engine); QV4::Function *v4Function = engine->currentStackFrame->v4Function; @@ -683,7 +696,7 @@ static Q_NEVER_INLINE ReturnedValue getElementFallback(ExecutionEngine *engine, return o->get(name); } -ReturnedValue Runtime::method_loadElement(ExecutionEngine *engine, const Value &object, const Value &index) +ReturnedValue Runtime::LoadElement::call(ExecutionEngine *engine, const Value &object, const Value &index) { if (index.isPositiveInt()) { uint idx = static_cast<uint>(index.int_32()); @@ -704,7 +717,7 @@ ReturnedValue Runtime::method_loadElement(ExecutionEngine *engine, const Value & return getElementFallback(engine, object, index); } -ReturnedValue Runtime::method_loadElement_traced(ExecutionEngine *engine, const Value &object, const Value &index, quint8 *traceSlot) +ReturnedValue Runtime::LoadElement_Traced::call(ExecutionEngine *engine, const Value &object, const Value &index, quint8 *traceSlot) { *traceSlot |= quint8(ObservedTraceValues::ArrayWasAccessed); if (index.isPositiveInt()) { @@ -761,7 +774,7 @@ static Q_NEVER_INLINE bool setElementFallback(ExecutionEngine *engine, const Val return o->put(name, value); } -void Runtime::method_storeElement(ExecutionEngine *engine, const Value &object, const Value &index, const Value &value) +void Runtime::StoreElement::call(ExecutionEngine *engine, const Value &object, const Value &index, const Value &value) { if (index.isPositiveInt()) { uint idx = static_cast<uint>(index.int_32()); @@ -783,7 +796,7 @@ void Runtime::method_storeElement(ExecutionEngine *engine, const Value &object, engine->throwTypeError(); } -void Runtime::method_storeElement_traced(ExecutionEngine *engine, const Value &object, const Value &index, const Value &value, quint8 *traceSlot) +void Runtime::StoreElement_traced::call(ExecutionEngine *engine, const Value &object, const Value &index, const Value &value, quint8 *traceSlot) { *traceSlot |= quint8(ObservedTraceValues::ArrayWasAccessed); if (index.isPositiveInt()) { @@ -807,7 +820,7 @@ void Runtime::method_storeElement_traced(ExecutionEngine *engine, const Value &o engine->throwTypeError(); } -ReturnedValue Runtime::method_getIterator(ExecutionEngine *engine, const Value &in, int iterator) +ReturnedValue Runtime::GetIterator::call(ExecutionEngine *engine, const Value &in, int iterator) { Scope scope(engine); ScopedObject o(scope, (Object *)nullptr); @@ -830,7 +843,7 @@ ReturnedValue Runtime::method_getIterator(ExecutionEngine *engine, const Value & return engine->newForInIteratorObject(o)->asReturnedValue(); } -ReturnedValue Runtime::method_iteratorNext(ExecutionEngine *engine, const Value &iterator, Value *value) +ReturnedValue Runtime::IteratorNext::call(ExecutionEngine *engine, const Value &iterator, Value *value) { // if we throw an exception from here, return true, not undefined. This is to ensure iteratorDone is set to true // and the stack unwinding won't close the iterator @@ -866,7 +879,7 @@ ReturnedValue Runtime::method_iteratorNext(ExecutionEngine *engine, const Value return Encode(false); } -ReturnedValue Runtime::method_iteratorNextForYieldStar(ExecutionEngine *engine, const Value &received, const Value &iterator, Value *object) +ReturnedValue Runtime::IteratorNextForYieldStar::call(ExecutionEngine *engine, const Value &received, const Value &iterator, Value *object) { // the return value encodes how to continue the yield* iteration. // true implies iteration is done, false for iteration to continue @@ -903,7 +916,7 @@ ReturnedValue Runtime::method_iteratorNextForYieldStar(ExecutionEngine *engine, if (t->isUndefined()) { // no throw method on the iterator ScopedValue done(scope, Encode(false)); - method_iteratorClose(engine, iterator, done); + IteratorClose::call(engine, iterator, done); if (engine->hasException) return Encode::undefined(); return engine->throwTypeError(); @@ -938,7 +951,7 @@ ReturnedValue Runtime::method_iteratorNextForYieldStar(ExecutionEngine *engine, return Encode(false); } -ReturnedValue Runtime::method_iteratorClose(ExecutionEngine *engine, const Value &iterator, const Value &done) +ReturnedValue Runtime::IteratorClose::call(ExecutionEngine *engine, const Value &iterator, const Value &done) { Q_ASSERT(iterator.isObject()); Q_ASSERT(done.isBoolean()); @@ -978,7 +991,7 @@ ReturnedValue Runtime::method_iteratorClose(ExecutionEngine *engine, const Value return originalCompletion(); } -ReturnedValue Runtime::method_destructureRestElement(ExecutionEngine *engine, const Value &iterator) +ReturnedValue Runtime::DestructureRestElement::call(ExecutionEngine *engine, const Value &iterator) { Q_ASSERT(iterator.isObject()); @@ -988,7 +1001,7 @@ ReturnedValue Runtime::method_destructureRestElement(ExecutionEngine *engine, co uint index = 0; while (1) { ScopedValue n(scope); - ScopedValue done(scope, method_iteratorNext(engine, iterator, n)); + ScopedValue done(scope, IteratorNext::call(engine, iterator, n)); if (engine->hasException) return Encode::undefined(); Q_ASSERT(done->isBoolean()); @@ -1000,7 +1013,7 @@ ReturnedValue Runtime::method_destructureRestElement(ExecutionEngine *engine, co return array->asReturnedValue(); } -void Runtime::method_storeNameSloppy(ExecutionEngine *engine, int nameIndex, const Value &value) +void Runtime::StoreNameSloppy::call(ExecutionEngine *engine, int nameIndex, const Value &value) { Scope scope(engine); ScopedString name(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]); @@ -1010,7 +1023,7 @@ void Runtime::method_storeNameSloppy(ExecutionEngine *engine, int nameIndex, con engine->globalObject->put(name, value); } -void Runtime::method_storeNameStrict(ExecutionEngine *engine, int nameIndex, const Value &value) +void Runtime::StoreNameStrict::call(ExecutionEngine *engine, int nameIndex, const Value &value) { Scope scope(engine); ScopedString name(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]); @@ -1021,7 +1034,7 @@ void Runtime::method_storeNameStrict(ExecutionEngine *engine, int nameIndex, con engine->throwReferenceError(name); } -ReturnedValue Runtime::method_loadProperty(ExecutionEngine *engine, const Value &object, int nameIndex) +ReturnedValue Runtime::LoadProperty::call(ExecutionEngine *engine, const Value &object, int nameIndex) { Scope scope(engine); ScopedString name(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]); @@ -1041,7 +1054,7 @@ ReturnedValue Runtime::method_loadProperty(ExecutionEngine *engine, const Value return o->get(name); } -ReturnedValue Runtime::method_loadName(ExecutionEngine *engine, int nameIndex) +ReturnedValue Runtime::LoadName::call(ExecutionEngine *engine, int nameIndex) { Scope scope(engine); ScopedString name(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]); @@ -1084,7 +1097,7 @@ static Object *getSuperBase(Scope &scope) return proto; } -ReturnedValue Runtime::method_loadSuperProperty(ExecutionEngine *engine, const Value &property) +ReturnedValue Runtime::LoadSuperProperty::call(ExecutionEngine *engine, const Value &property) { Scope scope(engine); Object *base = getSuperBase(scope); @@ -1096,7 +1109,7 @@ ReturnedValue Runtime::method_loadSuperProperty(ExecutionEngine *engine, const V return base->get(key, &engine->currentStackFrame->jsFrame->thisObject); } -void Runtime::method_storeSuperProperty(ExecutionEngine *engine, const Value &property, const Value &value) +void Runtime::StoreSuperProperty::call(ExecutionEngine *engine, const Value &property, const Value &value) { Scope scope(engine); Object *base = getSuperBase(scope); @@ -1110,7 +1123,40 @@ void Runtime::method_storeSuperProperty(ExecutionEngine *engine, const Value &pr engine->throwTypeError(); } -ReturnedValue Runtime::method_loadSuperConstructor(ExecutionEngine *engine, const Value &t) +ReturnedValue Runtime::LoadGlobalLookup::call(ExecutionEngine *engine, Function *f, int index) +{ + Lookup *l = f->compilationUnit->runtimeLookups + index; + return l->globalGetter(l, engine); +} + +ReturnedValue Runtime::LoadQmlContextPropertyLookup::call(ExecutionEngine *engine, uint index) +{ + Lookup *l = engine->currentStackFrame->v4Function->compilationUnit->runtimeLookups + index; + return l->qmlContextPropertyGetter(l, engine, nullptr); +} + +ReturnedValue Runtime::GetLookup::call(ExecutionEngine *engine, Function *f, const Value &base, int index) +{ + Lookup *l = f->compilationUnit->runtimeLookups + index; + return l->getter(l, engine, base); +} + +void Runtime::SetLookupSloppy::call(Function *f, const Value &base, int index, const Value &value) +{ + ExecutionEngine *engine = f->internalClass->engine; + QV4::Lookup *l = f->compilationUnit->runtimeLookups + index; + l->setter(l, engine, const_cast<Value &>(base), value); +} + +void Runtime::SetLookupStrict::call(Function *f, const Value &base, int index, const Value &value) +{ + ExecutionEngine *engine = f->internalClass->engine; + QV4::Lookup *l = f->compilationUnit->runtimeLookups + index; + if (!l->setter(l, engine, const_cast<Value &>(base), value)) + engine->throwTypeError(); +} + +ReturnedValue Runtime::LoadSuperConstructor::call(ExecutionEngine *engine, const Value &t) { if (engine->currentStackFrame->thisObject() != Value::emptyValue().asReturnedValue()) { return engine->throwReferenceError(QStringLiteral("super() already called."), QString(), 0, 0); // ### fix line number @@ -1143,9 +1189,9 @@ uint RuntimeHelpers::equalHelper(const Value &x, const Value &y) double dx = RuntimeHelpers::toNumber(x); return dx == y.asDouble(); } else if (x.isBoolean()) { - return Runtime::method_compareEqual(Value::fromDouble((double) x.booleanValue()), y); + return Runtime::CompareEqual::call(Value::fromDouble((double) x.booleanValue()), y); } else if (y.isBoolean()) { - return Runtime::method_compareEqual(x, Value::fromDouble((double) y.booleanValue())); + return Runtime::CompareEqual::call(x, Value::fromDouble((double) y.booleanValue())); } else { #ifdef V4_BOOTSTRAP Q_UNIMPLEMENTED(); @@ -1155,11 +1201,11 @@ uint RuntimeHelpers::equalHelper(const Value &x, const Value &y) if (yo && (x.isNumber() || x.isString())) { Scope scope(yo->engine()); ScopedValue py(scope, RuntimeHelpers::objectDefaultValue(yo, PREFERREDTYPE_HINT)); - return Runtime::method_compareEqual(x, py); + return Runtime::CompareEqual::call(x, py); } else if (xo && (y.isNumber() || y.isString())) { Scope scope(xo->engine()); ScopedValue px(scope, RuntimeHelpers::objectDefaultValue(xo, PREFERREDTYPE_HINT)); - return Runtime::method_compareEqual(px, y); + return Runtime::CompareEqual::call(px, y); } #endif } @@ -1182,7 +1228,7 @@ Bool RuntimeHelpers::strictEqual(const Value &x, const Value &y) return false; } -QV4::Bool Runtime::method_compareGreaterThan(const Value &l, const Value &r) +QV4::Bool Runtime::CompareGreaterThan::call(const Value &l, const Value &r) { TRACE2(l, r); if (l.isInteger() && r.isInteger()) @@ -1210,7 +1256,7 @@ QV4::Bool Runtime::method_compareGreaterThan(const Value &l, const Value &r) QV4::Scope scope(e); QV4::ScopedValue pl(scope, lo ? RuntimeHelpers::objectDefaultValue(lo, QV4::NUMBER_HINT) : l.asReturnedValue()); QV4::ScopedValue pr(scope, ro ? RuntimeHelpers::objectDefaultValue(ro, QV4::NUMBER_HINT) : r.asReturnedValue()); - return Runtime::method_compareGreaterThan(pl, pr); + return Runtime::CompareGreaterThan::call(pl, pr); #endif } @@ -1219,7 +1265,7 @@ QV4::Bool Runtime::method_compareGreaterThan(const Value &l, const Value &r) return dl > dr; } -QV4::Bool Runtime::method_compareLessThan(const Value &l, const Value &r) +QV4::Bool Runtime::CompareLessThan::call(const Value &l, const Value &r) { TRACE2(l, r); if (l.isInteger() && r.isInteger()) @@ -1247,7 +1293,7 @@ QV4::Bool Runtime::method_compareLessThan(const Value &l, const Value &r) QV4::Scope scope(e); QV4::ScopedValue pl(scope, lo ? RuntimeHelpers::objectDefaultValue(lo, QV4::NUMBER_HINT) : l.asReturnedValue()); QV4::ScopedValue pr(scope, ro ? RuntimeHelpers::objectDefaultValue(ro, QV4::NUMBER_HINT) : r.asReturnedValue()); - return Runtime::method_compareLessThan(pl, pr); + return Runtime::CompareLessThan::call(pl, pr); #endif } @@ -1256,7 +1302,7 @@ QV4::Bool Runtime::method_compareLessThan(const Value &l, const Value &r) return dl < dr; } -QV4::Bool Runtime::method_compareGreaterEqual(const Value &l, const Value &r) +QV4::Bool Runtime::CompareGreaterEqual::call(const Value &l, const Value &r) { TRACE2(l, r); if (l.isInteger() && r.isInteger()) @@ -1284,7 +1330,7 @@ QV4::Bool Runtime::method_compareGreaterEqual(const Value &l, const Value &r) QV4::Scope scope(e); QV4::ScopedValue pl(scope, lo ? RuntimeHelpers::objectDefaultValue(lo, QV4::NUMBER_HINT) : l.asReturnedValue()); QV4::ScopedValue pr(scope, ro ? RuntimeHelpers::objectDefaultValue(ro, QV4::NUMBER_HINT) : r.asReturnedValue()); - return Runtime::method_compareGreaterEqual(pl, pr); + return Runtime::CompareGreaterEqual::call(pl, pr); #endif } @@ -1293,7 +1339,7 @@ QV4::Bool Runtime::method_compareGreaterEqual(const Value &l, const Value &r) return dl >= dr; } -QV4::Bool Runtime::method_compareLessEqual(const Value &l, const Value &r) +QV4::Bool Runtime::CompareLessEqual::call(const Value &l, const Value &r) { TRACE2(l, r); if (l.isInteger() && r.isInteger()) @@ -1321,7 +1367,7 @@ QV4::Bool Runtime::method_compareLessEqual(const Value &l, const Value &r) QV4::Scope scope(e); QV4::ScopedValue pl(scope, lo ? RuntimeHelpers::objectDefaultValue(lo, QV4::NUMBER_HINT) : l.asReturnedValue()); QV4::ScopedValue pr(scope, ro ? RuntimeHelpers::objectDefaultValue(ro, QV4::NUMBER_HINT) : r.asReturnedValue()); - return Runtime::method_compareLessEqual(pl, pr); + return Runtime::CompareLessEqual::call(pl, pr); #endif } @@ -1331,21 +1377,21 @@ QV4::Bool Runtime::method_compareLessEqual(const Value &l, const Value &r) } #ifndef V4_BOOTSTRAP -Bool Runtime::method_compareInstanceof(ExecutionEngine *engine, const Value &left, const Value &right) +Bool Runtime::CompareInstanceof::call(ExecutionEngine *engine, const Value &left, const Value &right) { TRACE2(left, right); Scope scope(engine); - ScopedValue v(scope, method_instanceof(engine, left, right)); + ScopedValue v(scope, Instanceof::call(engine, left, right)); return v->booleanValue(); } -uint Runtime::method_compareIn(ExecutionEngine *engine, const Value &left, const Value &right) +uint Runtime::CompareIn::call(ExecutionEngine *engine, const Value &left, const Value &right) { TRACE2(left, right); Scope scope(engine); - ScopedValue v(scope, method_in(engine, left, right)); + ScopedValue v(scope, In::call(engine, left, right)); return v->booleanValue(); } @@ -1359,7 +1405,7 @@ static ReturnedValue throwPropertyIsNotAFunctionTypeError(ExecutionEngine *engin return engine->throwTypeError(msg); } -ReturnedValue Runtime::method_callGlobalLookup(ExecutionEngine *engine, uint index, Value *argv, int argc) +ReturnedValue Runtime::CallGlobalLookup::call(ExecutionEngine *engine, uint index, Value argv[], int argc) { Scope scope(engine); Lookup *l = engine->currentStackFrame->v4Function->compilationUnit->runtimeLookups + index; @@ -1372,7 +1418,8 @@ ReturnedValue Runtime::method_callGlobalLookup(ExecutionEngine *engine, uint ind return static_cast<FunctionObject &>(function).call(&thisObject, argv, argc); } -ReturnedValue Runtime::method_callQmlContextPropertyLookup(ExecutionEngine *engine, uint index, Value *argv, int argc) +ReturnedValue Runtime::CallQmlContextPropertyLookup::call(ExecutionEngine *engine, uint index, + Value *argv, int argc) { Scope scope(engine); ScopedValue thisObject(scope); @@ -1385,7 +1432,7 @@ ReturnedValue Runtime::method_callQmlContextPropertyLookup(ExecutionEngine *engi return static_cast<FunctionObject &>(function).call(thisObject, argv, argc); } -ReturnedValue Runtime::method_callPossiblyDirectEval(ExecutionEngine *engine, Value *argv, int argc) +ReturnedValue Runtime::CallPossiblyDirectEval::call(ExecutionEngine *engine, Value *argv, int argc) { Scope scope(engine); ScopedValue thisObject(scope); @@ -1404,7 +1451,7 @@ ReturnedValue Runtime::method_callPossiblyDirectEval(ExecutionEngine *engine, Va return function->call(thisObject, argv, argc); } -ReturnedValue Runtime::method_callName(ExecutionEngine *engine, int nameIndex, Value *argv, int argc) +ReturnedValue Runtime::CallName::call(ExecutionEngine *engine, int nameIndex, Value *argv, int argc) { Scope scope(engine); ScopedValue thisObject(scope); @@ -1422,8 +1469,9 @@ ReturnedValue Runtime::method_callName(ExecutionEngine *engine, int nameIndex, V return f->call(thisObject, argv, argc); } -ReturnedValue Runtime::method_callProperty(ExecutionEngine *engine, Value *base, int nameIndex, Value *argv, int argc) +ReturnedValue Runtime::CallProperty::call(ExecutionEngine *engine, const Value &baseRef, int nameIndex, Value *argv, int argc) { + const Value *base = &baseRef; Scope scope(engine); ScopedString name(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]); ScopedObject lookupObject(scope, base); @@ -1437,7 +1485,7 @@ ReturnedValue Runtime::method_callProperty(ExecutionEngine *engine, Value *base, } if (base->isManaged()) { - Managed *m = static_cast<Managed *>(base); + const Managed *m = static_cast<const Managed *>(base); lookupObject = m->internalClass()->prototype; Q_ASSERT(m->internalClass()->prototype); } else { @@ -1461,20 +1509,21 @@ ReturnedValue Runtime::method_callProperty(ExecutionEngine *engine, Value *base, return f->call(base, argv, argc); } -ReturnedValue Runtime::method_callPropertyLookup(ExecutionEngine *engine, Value *base, uint index, Value *argv, int argc) +ReturnedValue Runtime::CallPropertyLookup::call(ExecutionEngine *engine, const Value &base, uint index, Value *argv, int argc) { Lookup *l = engine->currentStackFrame->v4Function->compilationUnit->runtimeLookups + index; // ok to have the value on the stack here - Value f = Value::fromReturnedValue(l->getter(l, engine, *base)); + Value f = Value::fromReturnedValue(l->getter(l, engine, base)); if (!f.isFunctionObject()) return engine->throwTypeError(); - return static_cast<FunctionObject &>(f).call(base, argv, argc); + return static_cast<FunctionObject &>(f).call(&base, argv, argc); } -ReturnedValue Runtime::method_callElement(ExecutionEngine *engine, Value *base, const Value &index, Value *argv, int argc) +ReturnedValue Runtime::CallElement::call(ExecutionEngine *engine, const Value &baseRef, const Value &index, Value *argv, int argc) { + const Value *base = &baseRef; Scope scope(engine); ScopedValue thisObject(scope, base->toObject(engine)); base = thisObject; @@ -1483,14 +1532,14 @@ ReturnedValue Runtime::method_callElement(ExecutionEngine *engine, Value *base, if (engine->hasException) return Encode::undefined(); - ScopedFunctionObject f(scope, static_cast<Object *>(base)->get(str)); + ScopedFunctionObject f(scope, static_cast<const Object *>(base)->get(str)); if (!f) return engine->throwTypeError(); return f->call(base, argv, argc); } -ReturnedValue Runtime::method_callValue(ExecutionEngine *engine, const Value &func, Value *argv, int argc) +ReturnedValue Runtime::CallValue::call(ExecutionEngine *engine, const Value &func, Value *argv, int argc) { if (!func.isFunctionObject()) return engine->throwTypeError(QStringLiteral("%1 is not a function").arg(func.toQStringNoThrow())); @@ -1498,11 +1547,12 @@ ReturnedValue Runtime::method_callValue(ExecutionEngine *engine, const Value &fu return static_cast<const FunctionObject &>(func).call(&undef, argv, argc); } -ReturnedValue Runtime::method_callWithReceiver(ExecutionEngine *engine, const Value &func, const Value *thisObject, Value *argv, int argc) +ReturnedValue Runtime::CallWithReceiver::call(ExecutionEngine *engine, const Value &func, + const Value &thisObject, Value argv[], int argc) { if (!func.isFunctionObject()) return engine->throwTypeError(QStringLiteral("%1 is not a function").arg(func.toQStringNoThrow())); - return static_cast<const FunctionObject &>(func).call(thisObject, argv, argc); + return static_cast<const FunctionObject &>(func).call(&thisObject, argv, argc); } struct CallArgs { @@ -1528,11 +1578,11 @@ static CallArgs createSpreadArguments(Scope &scope, Value *argv, int argc) } // spread element ++i; - it = Runtime::method_getIterator(scope.engine, argv[i], /* ForInIterator */ 1); + it = Runtime::GetIterator::call(scope.engine, argv[i], /* ForInIterator */ 1); if (scope.engine->hasException) return { nullptr, 0 }; while (1) { - done = Runtime::method_iteratorNext(scope.engine, it, v); + done = Runtime::IteratorNext::call(scope.engine, it, v); if (scope.engine->hasException) return { nullptr, 0 }; Q_ASSERT(done->isBoolean()); @@ -1545,7 +1595,7 @@ static CallArgs createSpreadArguments(Scope &scope, Value *argv, int argc) return { arguments, argCount }; } -ReturnedValue Runtime::method_callWithSpread(ExecutionEngine *engine, const Value &function, const Value &thisObject, Value *argv, int argc) +ReturnedValue Runtime::CallWithSpread::call(ExecutionEngine *engine, const Value &function, const Value &thisObject, Value *argv, int argc) { Q_ASSERT(argc >= 1); if (!function.isFunctionObject()) @@ -1559,7 +1609,7 @@ ReturnedValue Runtime::method_callWithSpread(ExecutionEngine *engine, const Valu return static_cast<const FunctionObject &>(function).call(&thisObject, arguments.argv, arguments.argc); } -ReturnedValue Runtime::method_construct(ExecutionEngine *engine, const Value &function, const Value &newTarget, Value *argv, int argc) +ReturnedValue Runtime::Construct::call(ExecutionEngine *engine, const Value &function, const Value &newTarget, Value *argv, int argc) { if (!function.isFunctionObject()) return engine->throwTypeError(); @@ -1567,7 +1617,7 @@ ReturnedValue Runtime::method_construct(ExecutionEngine *engine, const Value &fu return static_cast<const FunctionObject &>(function).callAsConstructor(argv, argc, &newTarget); } -ReturnedValue Runtime::method_constructWithSpread(ExecutionEngine *engine, const Value &function, const Value &newTarget, Value *argv, int argc) +ReturnedValue Runtime::ConstructWithSpread::call(ExecutionEngine *engine, const Value &function, const Value &newTarget, Value *argv, int argc) { if (!function.isFunctionObject()) return engine->throwTypeError(); @@ -1580,7 +1630,7 @@ ReturnedValue Runtime::method_constructWithSpread(ExecutionEngine *engine, const return static_cast<const FunctionObject &>(function).callAsConstructor(arguments.argv, arguments.argc, &newTarget); } -ReturnedValue Runtime::method_tailCall(CppStackFrame *frame, ExecutionEngine *engine) +ReturnedValue Runtime::TailCall::call(CppStackFrame *frame, ExecutionEngine *engine) { // IMPORTANT! The JIT assumes that this method has the same amount (or less) arguments than // the jitted function, so it can safely do a tail call. @@ -1610,13 +1660,13 @@ ReturnedValue Runtime::method_tailCall(CppStackFrame *frame, ExecutionEngine *en return Encode::undefined(); } -void Runtime::method_throwException(ExecutionEngine *engine, const Value &value) +void Runtime::ThrowException::call(ExecutionEngine *engine, const Value &value) { if (!value.isEmpty()) engine->throwError(value); } -ReturnedValue Runtime::method_typeofValue(ExecutionEngine *engine, const Value &value) +ReturnedValue Runtime::TypeofValue::call(ExecutionEngine *engine, const Value &value) { Scope scope(engine); ScopedString res(scope); @@ -1647,83 +1697,109 @@ ReturnedValue Runtime::method_typeofValue(ExecutionEngine *engine, const Value & return res.asReturnedValue(); } -QV4::ReturnedValue Runtime::method_typeofName(ExecutionEngine *engine, int nameIndex) +QV4::ReturnedValue Runtime::TypeofName::call(ExecutionEngine *engine, int nameIndex) { Scope scope(engine); ScopedString name(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]); ScopedValue prop(scope, static_cast<ExecutionContext &>(engine->currentStackFrame->jsFrame->context).getProperty(name)); // typeof doesn't throw. clear any possible exception scope.engine->hasException = false; - return method_typeofValue(engine, prop); + return TypeofValue::call(engine, prop); } -ReturnedValue Runtime::method_createWithContext(ExecutionEngine *engine, Value *jsStackFrame) +void Runtime::PushCallContext::call(CppStackFrame *frame) { - QV4::Value &accumulator = jsStackFrame[CallData::Accumulator]; - accumulator = accumulator.toObject(engine); - if (engine->hasException) - return Encode::undefined(); - Q_ASSERT(accumulator.isObject()); - const Object &obj = static_cast<const Object &>(accumulator); - ExecutionContext *context = static_cast<ExecutionContext *>(jsStackFrame + CallData::Context); - return context->newWithContext(obj.d())->asReturnedValue(); + frame->jsFrame->context = ExecutionContext::newCallContext(frame)->asReturnedValue(); } -ReturnedValue Runtime::method_createCatchContext(ExecutionContext *parent, int blockIndex, int exceptionVarNameIndex) +ReturnedValue Runtime::PushWithContext::call(ExecutionEngine *engine, const Value &acc) { - ExecutionEngine *e = parent->engine(); - return parent->newCatchContext(e->currentStackFrame, blockIndex, - e->currentStackFrame->v4Function->compilationUnit->runtimeStrings[exceptionVarNameIndex])->asReturnedValue(); + CallData *jsFrame = engine->currentStackFrame->jsFrame; + Value &newAcc = jsFrame->accumulator; + newAcc = Value::fromHeapObject(acc.toObject(engine)); + if (!engine->hasException) { + Q_ASSERT(newAcc.isObject()); + const Object &obj = static_cast<const Object &>(newAcc); + Value &context = jsFrame->context; + auto ec = static_cast<const ExecutionContext *>(&context); + context = ec->newWithContext(obj.d())->asReturnedValue(); + } + return newAcc.asReturnedValue(); } -ReturnedValue Runtime::method_createBlockContext(ExecutionContext *parent, int index) +void Runtime::PushCatchContext::call(ExecutionEngine *engine, int blockIndex, int exceptionVarNameIndex) { - ExecutionEngine *e = parent->engine(); - return parent->newBlockContext(e->currentStackFrame, index)->asReturnedValue(); + auto name = engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[exceptionVarNameIndex]; + engine->currentStackFrame->jsFrame->context = ExecutionContext::newCatchContext(engine->currentStackFrame, blockIndex, name)->asReturnedValue(); } -ReturnedValue Runtime::method_cloneBlockContext(ExecutionContext *previous) +void Runtime::PushBlockContext::call(ExecutionEngine *engine, int index) { - return ExecutionContext::cloneBlockContext(static_cast<Heap::CallContext *>(previous->d()))->asReturnedValue(); + engine->currentStackFrame->jsFrame->context = ExecutionContext::newBlockContext(engine->currentStackFrame, index)->asReturnedValue(); } +void Runtime::CloneBlockContext::call(ExecutionEngine *engine) +{ + auto frame = engine->currentStackFrame; + auto context = static_cast<Heap::CallContext *>(frame->jsFrame->context.m()); + frame->jsFrame->context = + ExecutionContext::cloneBlockContext(engine, context)->asReturnedValue(); +} -ReturnedValue Runtime::method_createScriptContext(ExecutionEngine *engine, int index) +void Runtime::PushScriptContext::call(ExecutionEngine *engine, int index) { Q_ASSERT(engine->currentStackFrame->context()->d()->type == Heap::ExecutionContext::Type_GlobalContext || engine->currentStackFrame->context()->d()->type == Heap::ExecutionContext::Type_QmlContext); ReturnedValue c = ExecutionContext::newBlockContext(engine->currentStackFrame, index)->asReturnedValue(); engine->setScriptContext(c); - return c; + engine->currentStackFrame->jsFrame->context = c; } -ReturnedValue Runtime::method_popScriptContext(ExecutionEngine *engine) +void Runtime::PopScriptContext::call(ExecutionEngine *engine) { ReturnedValue root = engine->rootContext()->asReturnedValue(); engine->setScriptContext(root); - return root; + engine->currentStackFrame->jsFrame->context = root; } -void Runtime::method_throwReferenceError(ExecutionEngine *engine, int nameIndex) +void Runtime::ThrowReferenceError::call(ExecutionEngine *engine, int nameIndex) { Scope scope(engine); ScopedString name(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]); engine->throwReferenceError(name); } -void Runtime::method_declareVar(ExecutionEngine *engine, bool deletable, int nameIndex) +void Runtime::ThrowOnNullOrUndefined::call(ExecutionEngine *engine, const Value &v) +{ + if (v.isNullOrUndefined()) + engine->throwTypeError(); +} + +ReturnedValue Runtime::ConvertThisToObject::call(ExecutionEngine *engine, const Value &t) +{ + if (!t.isObject()) { + if (t.isNullOrUndefined()) { + return engine->globalObject->asReturnedValue(); + } else { + return t.toObject(engine)->asReturnedValue(); + } + } + return t.asReturnedValue(); +} + +void Runtime::DeclareVar::call(ExecutionEngine *engine, Bool deletable, int nameIndex) { Scope scope(engine); ScopedString name(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[nameIndex]); static_cast<ExecutionContext &>(engine->currentStackFrame->jsFrame->context).createMutableBinding(name, deletable); } -ReturnedValue Runtime::method_arrayLiteral(ExecutionEngine *engine, Value *values, uint length) +ReturnedValue Runtime::ArrayLiteral::call(ExecutionEngine *engine, Value *values, uint length) { return engine->newArrayObject(values, length)->asReturnedValue(); } -ReturnedValue Runtime::method_objectLiteral(ExecutionEngine *engine, int classId, const QV4::Value *args, int argc) +ReturnedValue Runtime::ObjectLiteral::call(ExecutionEngine *engine, int classId, QV4::Value args[], int argc) { Scope scope(engine); Scoped<InternalClass> klass(scope, engine->currentStackFrame->v4Function->compilationUnit->runtimeClasses[classId]); @@ -1796,7 +1872,8 @@ ReturnedValue Runtime::method_objectLiteral(ExecutionEngine *engine, int classId return o.asReturnedValue(); } -ReturnedValue Runtime::method_createClass(ExecutionEngine *engine, int classIndex, const Value &superClass, const Value *computedNames) +ReturnedValue Runtime::CreateClass::call(ExecutionEngine *engine, int classIndex, + const Value &superClass, Value computedNames[]) { const CompiledData::CompilationUnit *unit = engine->currentStackFrame->v4Function->compilationUnit; const QV4::CompiledData::Class *cls = unit->unitData()->classAt(classIndex); @@ -1898,20 +1975,20 @@ ReturnedValue Runtime::method_createClass(ExecutionEngine *engine, int classInde return constructor->asReturnedValue(); } -QV4::ReturnedValue Runtime::method_createMappedArgumentsObject(ExecutionEngine *engine) +QV4::ReturnedValue Runtime::CreateMappedArgumentsObject::call(ExecutionEngine *engine) { Q_ASSERT(engine->currentContext()->d()->type == Heap::ExecutionContext::Type_CallContext); Heap::InternalClass *ic = engine->internalClasses(EngineBase::Class_ArgumentsObject); return engine->memoryManager->allocObject<ArgumentsObject>(ic, engine->currentStackFrame)->asReturnedValue(); } -QV4::ReturnedValue Runtime::method_createUnmappedArgumentsObject(ExecutionEngine *engine) +QV4::ReturnedValue Runtime::CreateUnmappedArgumentsObject::call(ExecutionEngine *engine) { Heap::InternalClass *ic = engine->internalClasses(EngineBase::Class_StrictArgumentsObject); return engine->memoryManager->allocObject<StrictArgumentsObject>(ic, engine->currentStackFrame)->asReturnedValue(); } -QV4::ReturnedValue Runtime::method_createRestParameter(ExecutionEngine *engine, int argIndex) +QV4::ReturnedValue Runtime::CreateRestParameter::call(ExecutionEngine *engine, int argIndex) { const Value *values = engine->currentStackFrame->originalArguments + argIndex; int nValues = engine->currentStackFrame->originalArgumentsCount - argIndex; @@ -1920,14 +1997,32 @@ QV4::ReturnedValue Runtime::method_createRestParameter(ExecutionEngine *engine, return engine->newArrayObject(values, nValues)->asReturnedValue(); } -ReturnedValue Runtime::method_regexpLiteral(ExecutionEngine *engine, int id) +ReturnedValue Runtime::RegexpLiteral::call(ExecutionEngine *engine, int id) { Heap::RegExpObject *ro = engine->newRegExpObject(engine->currentStackFrame->v4Function->compilationUnit->runtimeRegularExpressions[id].as<RegExp>()); return ro->asReturnedValue(); } + +ReturnedValue Runtime::ToObject::call(ExecutionEngine *engine, const Value &obj) +{ + if (obj.isObject()) + return obj.asReturnedValue(); + + return obj.toObject(engine)->asReturnedValue(); +} + +Bool Runtime::ToBoolean::call(const Value &obj) +{ + return obj.toBoolean(); +} + +ReturnedValue Runtime::ToNumber::call(ExecutionEngine *, const Value &v) +{ + return Encode(v.toNumber()); +} #endif // V4_BOOTSTRAP -ReturnedValue Runtime::method_uMinus(const Value &value) +ReturnedValue Runtime::UMinus::call(const Value &value) { TRACE1(value); @@ -1944,7 +2039,7 @@ ReturnedValue Runtime::method_uMinus(const Value &value) // binary operators #ifndef V4_BOOTSTRAP -ReturnedValue Runtime::method_add(ExecutionEngine *engine, const Value &left, const Value &right) +ReturnedValue Runtime::Add::call(ExecutionEngine *engine, const Value &left, const Value &right) { TRACE2(left, right); @@ -1956,7 +2051,7 @@ ReturnedValue Runtime::method_add(ExecutionEngine *engine, const Value &left, co return RuntimeHelpers::addHelper(engine, left, right); } -ReturnedValue Runtime::method_sub(const Value &left, const Value &right) +ReturnedValue Runtime::Sub::call(const Value &left, const Value &right) { TRACE2(left, right); @@ -1969,7 +2064,7 @@ ReturnedValue Runtime::method_sub(const Value &left, const Value &right) return Value::fromDouble(lval - rval).asReturnedValue(); } -ReturnedValue Runtime::method_mul(const Value &left, const Value &right) +ReturnedValue Runtime::Mul::call(const Value &left, const Value &right) { TRACE2(left, right); @@ -1982,7 +2077,7 @@ ReturnedValue Runtime::method_mul(const Value &left, const Value &right) return Value::fromDouble(lval * rval).asReturnedValue(); } -ReturnedValue Runtime::method_div(const Value &left, const Value &right) +ReturnedValue Runtime::Div::call(const Value &left, const Value &right) { TRACE2(left, right); @@ -2003,7 +2098,7 @@ ReturnedValue Runtime::method_div(const Value &left, const Value &right) return Value::fromDouble(lval / rval).asReturnedValue(); } -ReturnedValue Runtime::method_mod(const Value &left, const Value &right) +ReturnedValue Runtime::Mod::call(const Value &left, const Value &right) { TRACE2(left, right); @@ -2024,7 +2119,43 @@ ReturnedValue Runtime::method_mod(const Value &left, const Value &right) return Value::fromDouble(std::fmod(lval, rval)).asReturnedValue(); } -ReturnedValue Runtime::method_shl(const Value &left, const Value &right) +ReturnedValue Runtime::Exp::call(const Value &base, const Value &exp) +{ + double b = base.toNumber(); + double e = exp.toNumber(); + if (qt_is_inf(e) && (b == 1 || b == -1)) + return Encode(qt_snan()); + return Encode(pow(b,e)); +} + +ReturnedValue Runtime::BitAnd::call(const Value &left, const Value &right) +{ + TRACE2(left, right); + + int lval = left.toInt32(); + int rval = right.toInt32(); + return Encode((int)(lval & rval)); +} + +ReturnedValue Runtime::BitOr::call(const Value &left, const Value &right) +{ + TRACE2(left, right); + + int lval = left.toInt32(); + int rval = right.toInt32(); + return Encode((int)(lval | rval)); +} + +ReturnedValue Runtime::BitXor::call(const Value &left, const Value &right) +{ + TRACE2(left, right); + + int lval = left.toInt32(); + int rval = right.toInt32(); + return Encode((int)(lval ^ rval)); +} + +ReturnedValue Runtime::Shl::call(const Value &left, const Value &right) { TRACE2(left, right); @@ -2033,7 +2164,7 @@ ReturnedValue Runtime::method_shl(const Value &left, const Value &right) return Encode((int)(lval << rval)); } -ReturnedValue Runtime::method_shr(const Value &left, const Value &right) +ReturnedValue Runtime::Shr::call(const Value &left, const Value &right) { TRACE2(left, right); @@ -2042,7 +2173,7 @@ ReturnedValue Runtime::method_shr(const Value &left, const Value &right) return Encode((int)(lval >> rval)); } -ReturnedValue Runtime::method_ushr(const Value &left, const Value &right) +ReturnedValue Runtime::UShr::call(const Value &left, const Value &right) { TRACE2(left, right); @@ -2055,35 +2186,35 @@ ReturnedValue Runtime::method_ushr(const Value &left, const Value &right) #endif // V4_BOOTSTRAP -ReturnedValue Runtime::method_greaterThan(const Value &left, const Value &right) +ReturnedValue Runtime::GreaterThan::call(const Value &left, const Value &right) { TRACE2(left, right); - bool r = method_compareGreaterThan(left, right); + bool r = CompareGreaterThan::call(left, right); return Encode(r); } -ReturnedValue Runtime::method_lessThan(const Value &left, const Value &right) +ReturnedValue Runtime::LessThan::call(const Value &left, const Value &right) { TRACE2(left, right); - bool r = method_compareLessThan(left, right); + bool r = CompareLessThan::call(left, right); return Encode(r); } -ReturnedValue Runtime::method_greaterEqual(const Value &left, const Value &right) +ReturnedValue Runtime::GreaterEqual::call(const Value &left, const Value &right) { TRACE2(left, right); - bool r = method_compareGreaterEqual(left, right); + bool r = CompareGreaterEqual::call(left, right); return Encode(r); } -ReturnedValue Runtime::method_lessEqual(const Value &left, const Value &right) +ReturnedValue Runtime::LessEqual::call(const Value &left, const Value &right) { TRACE2(left, right); - bool r = method_compareLessEqual(left, right); + bool r = CompareLessEqual::call(left, right); return Encode(r); } @@ -2107,7 +2238,7 @@ struct LazyScope } }; -Bool Runtime::method_compareEqual(const Value &left, const Value &right) +Bool Runtime::CompareEqual::call(const Value &left, const Value &right) { TRACE2(left, right); @@ -2216,23 +2347,23 @@ Bool Runtime::method_compareEqual(const Value &left, const Value &right) } } -ReturnedValue Runtime::method_equal(const Value &left, const Value &right) +ReturnedValue Runtime::Equal::call(const Value &left, const Value &right) { TRACE2(left, right); - bool r = method_compareEqual(left, right); + bool r = CompareEqual::call(left, right); return Encode(r); } -ReturnedValue Runtime::method_notEqual(const Value &left, const Value &right) +ReturnedValue Runtime::NotEqual::call(const Value &left, const Value &right) { TRACE2(left, right); - bool r = !method_compareEqual(left, right); + bool r = !CompareEqual::call(left, right); return Encode(r); } -ReturnedValue Runtime::method_strictEqual(const Value &left, const Value &right) +ReturnedValue Runtime::StrictEqual::call(const Value &left, const Value &right) { TRACE2(left, right); @@ -2240,7 +2371,7 @@ ReturnedValue Runtime::method_strictEqual(const Value &left, const Value &right) return Encode(r); } -ReturnedValue Runtime::method_strictNotEqual(const Value &left, const Value &right) +ReturnedValue Runtime::StrictNotEqual::call(const Value &left, const Value &right) { TRACE2(left, right); @@ -2248,21 +2379,21 @@ ReturnedValue Runtime::method_strictNotEqual(const Value &left, const Value &rig return Encode(r); } -Bool Runtime::method_compareNotEqual(const Value &left, const Value &right) +Bool Runtime::CompareNotEqual::call(const Value &left, const Value &right) { TRACE2(left, right); - return !Runtime::method_compareEqual(left, right); + return !Runtime::CompareEqual::call(left, right); } -Bool Runtime::method_compareStrictEqual(const Value &left, const Value &right) +Bool Runtime::CompareStrictEqual::call(const Value &left, const Value &right) { TRACE2(left, right); return RuntimeHelpers::strictEqual(left, right); } -Bool Runtime::method_compareStrictNotEqual(const Value &left, const Value &right) +Bool Runtime::CompareStrictNotEqual::call(const Value &left, const Value &right) { TRACE2(left, right); diff --git a/src/qml/jsruntime/qv4runtime_p.h b/src/qml/jsruntime/qv4runtime_p.h index 2be3ebf012..72af90d1dc 100644 --- a/src/qml/jsruntime/qv4runtime_p.h +++ b/src/qml/jsruntime/qv4runtime_p.h @@ -114,8 +114,6 @@ struct Q_QML_PRIVATE_EXPORT RuntimeHelpers { static Bool strictEqual(const Value &x, const Value &y); static ReturnedValue addHelper(ExecutionEngine *engine, const Value &left, const Value &right); - - static ReturnedValue getTemplateObject(Function *function, int index); }; diff --git a/src/qml/jsruntime/qv4runtimeapi_p.h b/src/qml/jsruntime/qv4runtimeapi_p.h index ceec13a3bb..86cbccde23 100644 --- a/src/qml/jsruntime/qv4runtimeapi_p.h +++ b/src/qml/jsruntime/qv4runtimeapi_p.h @@ -57,173 +57,450 @@ QT_BEGIN_NAMESPACE namespace QV4 { typedef uint Bool; -struct NoThrowEngine; -namespace { -template <typename T> -struct ExceptionCheck { - enum { NeedsCheck = 1 }; -}; -// push_catch and pop context methods shouldn't check for exceptions -template <> -struct ExceptionCheck<void (*)(QV4::NoThrowEngine *)> { - enum { NeedsCheck = 0 }; -}; -template <typename A> -struct ExceptionCheck<void (*)(A, QV4::NoThrowEngine)> { - enum { NeedsCheck = 0 }; -}; -template <> -struct ExceptionCheck<QV4::ReturnedValue (*)(QV4::NoThrowEngine *)> { - enum { NeedsCheck = 0 }; -}; -template <typename A> -struct ExceptionCheck<QV4::ReturnedValue (*)(QV4::NoThrowEngine *, A)> { - enum { NeedsCheck = 0 }; -}; -template <typename A, typename B> -struct ExceptionCheck<QV4::ReturnedValue (*)(QV4::NoThrowEngine *, A, B)> { - enum { NeedsCheck = 0 }; -}; -template <typename A, typename B, typename C> -struct ExceptionCheck<void (*)(QV4::NoThrowEngine *, A, B, C)> { - enum { NeedsCheck = 0 }; -}; -} // anonymous namespace - -#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)) \ - F(ReturnedValue, callElement, (ExecutionEngine *engine, Value *base, const Value &index, Value *argv, int argc)) \ - F(ReturnedValue, callValue, (ExecutionEngine *engine, const Value &func, Value *argv, int argc)) \ - F(ReturnedValue, callWithReceiver, (ExecutionEngine *engine, const Value &func, const Value *thisObject, Value *argv, int argc)) \ - F(ReturnedValue, callPossiblyDirectEval, (ExecutionEngine *engine, Value *argv, int argc)) \ - F(ReturnedValue, callWithSpread, (ExecutionEngine *engine, const Value &func, const Value &thisObject, Value *argv, int argc)) \ - F(ReturnedValue, tailCall, (CppStackFrame *frame, ExecutionEngine *engine)) \ - \ - /* construct */ \ - F(ReturnedValue, construct, (ExecutionEngine *engine, const Value &func, const Value &newTarget, Value *argv, int argc)) \ - F(ReturnedValue, constructWithSpread, (ExecutionEngine *engine, const Value &func, const Value &newTarget, Value *argv, int argc)) \ - \ - /* load & store */ \ - F(void, storeNameStrict, (ExecutionEngine *engine, int nameIndex, const Value &value)) \ - F(void, storeNameSloppy, (ExecutionEngine *engine, int nameIndex, const Value &value)) \ - F(void, storeProperty, (ExecutionEngine *engine, const Value &object, int nameIndex, const Value &value)) \ - F(void, storeElement, (ExecutionEngine *engine, const Value &object, const Value &index, const Value &value)) \ - F(void, storeElement_traced, (ExecutionEngine *engine, const Value &object, const Value &index, const Value &value, quint8 *traceSlot)) \ - F(ReturnedValue, loadProperty, (ExecutionEngine *engine, const Value &object, int nameIndex)) \ - F(ReturnedValue, loadName, (ExecutionEngine *engine, int nameIndex)) \ - F(ReturnedValue, loadElement, (ExecutionEngine *engine, const Value &object, const Value &index)) \ - F(ReturnedValue, loadElement_traced, (ExecutionEngine *engine, const Value &object, const Value &index, quint8 *traceSlot)) \ - F(ReturnedValue, loadSuperProperty, (ExecutionEngine *engine, const Value &property)) \ - F(void, storeSuperProperty, (ExecutionEngine *engine, const Value &property, const Value &value)) \ - F(ReturnedValue, loadSuperConstructor, (ExecutionEngine *engine, const Value &t)) \ - \ - /* typeof */ \ - F(ReturnedValue, typeofValue, (ExecutionEngine *engine, const Value &val)) \ - F(ReturnedValue, typeofName, (ExecutionEngine *engine, int nameIndex)) \ - \ - /* delete */ \ - F(bool, deleteProperty, (ExecutionEngine *engine, const Value &base, const Value &index)) \ - F(bool, deleteName, (ExecutionEngine *engine, int nameIndex)) \ - \ - /* exceptions & scopes */ \ - F(void, throwException, (ExecutionEngine *engine, const Value &value)) \ - F(ReturnedValue, createWithContext, (ExecutionEngine *, Value *jsStackFrame)) \ - F(ReturnedValue, createCatchContext, (ExecutionContext *parent, int blockIndex, int exceptionVarNameIndex)) \ - F(ReturnedValue, createBlockContext, (ExecutionContext *parent, int index)) \ - F(ReturnedValue, createScriptContext, (ExecutionEngine *engine, int index)) \ - F(ReturnedValue, cloneBlockContext, (ExecutionContext *previous)) \ - F(ReturnedValue, popScriptContext, (ExecutionEngine *engine)) \ - F(void, throwReferenceError, (ExecutionEngine *engine, int nameIndex)) \ - \ - /* closures */ \ - F(ReturnedValue, closure, (ExecutionEngine *engine, int functionId)) \ - \ - /* function header */ \ - F(void, declareVar, (ExecutionEngine *engine, bool deletable, int nameIndex)) \ - F(ReturnedValue, createMappedArgumentsObject, (ExecutionEngine *engine)) \ - F(ReturnedValue, createUnmappedArgumentsObject, (ExecutionEngine *engine)) \ - F(ReturnedValue, createRestParameter, (ExecutionEngine *engine, int argIndex)) \ - \ - /* literals */ \ - F(ReturnedValue, arrayLiteral, (ExecutionEngine *engine, Value *values, uint length)) \ - F(ReturnedValue, objectLiteral, (ExecutionEngine *engine, int classId, const Value *args, int argc)) \ - F(ReturnedValue, createClass, (ExecutionEngine *engine, int classIndex, const Value &heritage, const Value *computedNames)) \ - \ - /* for-in, for-of and array destructuring */ \ - F(ReturnedValue, getIterator, (ExecutionEngine *engine, const Value &in, int iterator)) \ - F(ReturnedValue, iteratorNext, (ExecutionEngine *engine, const Value &iterator, Value *value)) \ - F(ReturnedValue, iteratorNextForYieldStar, (ExecutionEngine *engine, const Value &received, const Value &iterator, Value *object)) \ - F(ReturnedValue, iteratorClose, (ExecutionEngine *engine, const Value &iterator, const Value &done)) \ - F(ReturnedValue, destructureRestElement, (ExecutionEngine *engine, const Value &iterator)) \ - \ - /* unary operators */ \ - F(ReturnedValue, uMinus, (const Value &value)) \ - \ - /* binary operators */ \ - F(ReturnedValue, instanceof, (ExecutionEngine *engine, const Value &left, const Value &right)) \ - F(ReturnedValue, in, (ExecutionEngine *engine, const Value &left, const Value &right)) \ - F(ReturnedValue, add, (ExecutionEngine *engine, const Value &left, const Value &right)) \ - F(ReturnedValue, sub, (const Value &left, const Value &right)) \ - F(ReturnedValue, mul, (const Value &left, const Value &right)) \ - F(ReturnedValue, div, (const Value &left, const Value &right)) \ - F(ReturnedValue, mod, (const Value &left, const Value &right)) \ - F(ReturnedValue, shl, (const Value &left, const Value &right)) \ - F(ReturnedValue, shr, (const Value &left, const Value &right)) \ - F(ReturnedValue, ushr, (const Value &left, const Value &right)) \ - F(ReturnedValue, greaterThan, (const Value &left, const Value &right)) \ - F(ReturnedValue, lessThan, (const Value &left, const Value &right)) \ - F(ReturnedValue, greaterEqual, (const Value &left, const Value &right)) \ - F(ReturnedValue, lessEqual, (const Value &left, const Value &right)) \ - F(ReturnedValue, equal, (const Value &left, const Value &right)) \ - F(ReturnedValue, notEqual, (const Value &left, const Value &right)) \ - F(ReturnedValue, strictEqual, (const Value &left, const Value &right)) \ - F(ReturnedValue, strictNotEqual, (const Value &left, const Value &right)) \ - \ - /* comparisons */ \ - F(Bool, compareGreaterThan, (const Value &l, const Value &r)) \ - F(Bool, compareLessThan, (const Value &l, const Value &r)) \ - F(Bool, compareGreaterEqual, (const Value &l, const Value &r)) \ - F(Bool, compareLessEqual, (const Value &l, const Value &r)) \ - F(Bool, compareEqual, (const Value &left, const Value &right)) \ - F(Bool, compareNotEqual, (const Value &left, const Value &right)) \ - F(Bool, compareStrictEqual, (const Value &left, const Value &right)) \ - F(Bool, compareStrictNotEqual, (const Value &left, const Value &right)) \ - \ - 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)) struct Q_QML_PRIVATE_EXPORT Runtime { - Runtime(); - typedef ReturnedValue (*UnaryOperation)(const Value &value); typedef ReturnedValue (*BinaryOperation)(const Value &left, const Value &right); - typedef ReturnedValue (*BinaryOperationContext)(ExecutionEngine *engine, const Value &left, const Value &right); + typedef ReturnedValue (*BinaryOperationContext)(ExecutionEngine *, const Value &left, const Value &right); + + enum class Throws { No, Yes }; + enum class ChangesContext { No, Yes }; + enum class Pure { No, Yes }; + enum class LastArgumentIsOutputValue { No, Yes }; + + template<Throws t, ChangesContext c = ChangesContext::No, Pure p = Pure::No, + LastArgumentIsOutputValue out = LastArgumentIsOutputValue::No> + struct Method + { + static constexpr bool throws = t == Throws::Yes; + static constexpr bool changesContext = c == ChangesContext::Yes; + static constexpr bool pure = p == Pure::Yes; + static constexpr bool lastArgumentIsOutputValue = out == LastArgumentIsOutputValue::Yes; + }; + using PureMethod = Method<Throws::No, ChangesContext::No, Pure::Yes>; + using IteratorMethod = Method<Throws::Yes, ChangesContext::No, Pure::No, + LastArgumentIsOutputValue::Yes>; + + /* call */ + struct Q_QML_PRIVATE_EXPORT CallGlobalLookup : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, uint, Value[], int); + }; + struct Q_QML_PRIVATE_EXPORT CallQmlContextPropertyLookup : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, uint, Value[], int); + }; + struct Q_QML_PRIVATE_EXPORT CallName : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, int, Value[], int); + }; + struct Q_QML_PRIVATE_EXPORT CallProperty : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, int, Value[], int); + }; + struct Q_QML_PRIVATE_EXPORT CallPropertyLookup : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, uint, Value[], int); + }; + struct Q_QML_PRIVATE_EXPORT CallElement : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, const Value &, Value[], int); + }; + struct Q_QML_PRIVATE_EXPORT CallValue : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, Value[], int); + }; + struct Q_QML_PRIVATE_EXPORT CallWithReceiver : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, const Value &, Value[], int); + }; + struct Q_QML_PRIVATE_EXPORT CallPossiblyDirectEval : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, Value[], int); + }; + struct Q_QML_PRIVATE_EXPORT CallWithSpread : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, const Value &, Value[], int); + }; + struct Q_QML_PRIVATE_EXPORT TailCall : Method<Throws::Yes> + { + static ReturnedValue call(CppStackFrame *, ExecutionEngine *); + }; + + /* construct */ + struct Q_QML_PRIVATE_EXPORT Construct : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, const Value &, Value[], int); + }; + struct Q_QML_PRIVATE_EXPORT ConstructWithSpread : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, const Value &, Value[], int); + }; + + /* load & store */ + struct Q_QML_PRIVATE_EXPORT StoreNameStrict : Method<Throws::Yes> + { + static void call(ExecutionEngine *, int, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT StoreNameSloppy : Method<Throws::Yes> + { + static void call(ExecutionEngine *, int, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT StoreProperty : Method<Throws::Yes> + { + static void call(ExecutionEngine *, const Value &, int, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT StoreElement : Method<Throws::Yes> + { + static void call(ExecutionEngine *, const Value &, const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT StoreElement_traced : Method<Throws::Yes> + { + static void call(ExecutionEngine *, const Value &, const Value &, const Value &, quint8 *); + }; + struct Q_QML_PRIVATE_EXPORT LoadProperty : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, int); + }; + struct Q_QML_PRIVATE_EXPORT LoadName : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, int); + }; + struct Q_QML_PRIVATE_EXPORT LoadElement : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT LoadElement_Traced : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, const Value &, quint8 *); + }; + struct Q_QML_PRIVATE_EXPORT LoadSuperProperty : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT StoreSuperProperty : Method<Throws::Yes> + { + static void call(ExecutionEngine *, const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT LoadSuperConstructor : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT LoadGlobalLookup : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, Function *, int); + }; + struct Q_QML_PRIVATE_EXPORT LoadQmlContextPropertyLookup : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, uint); + }; + struct Q_QML_PRIVATE_EXPORT GetLookup : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, Function *, const Value &, int); + }; + struct Q_QML_PRIVATE_EXPORT SetLookupStrict : Method<Throws::Yes> + { + static void call(Function *, const Value &, int, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT SetLookupSloppy : Method<Throws::Yes> + { + static void call(Function *, const Value &, int, const Value &); + }; -#define DEFINE_RUNTIME_METHOD_ENUM(returnvalue, name, args) name, - enum RuntimeMethods { - FOR_EACH_RUNTIME_METHOD(DEFINE_RUNTIME_METHOD_ENUM) - RuntimeMethodCount, - InvalidRuntimeMethod = RuntimeMethodCount + /* typeof */ + struct Q_QML_PRIVATE_EXPORT TypeofValue : PureMethod + { + static ReturnedValue call(ExecutionEngine *, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT TypeofName : Method<Throws::No> + { + static ReturnedValue call(ExecutionEngine *, int); }; -#undef DEFINE_RUNTIME_METHOD_ENUM - void *runtimeMethods[RuntimeMethodCount]; + /* delete */ + struct Q_QML_PRIVATE_EXPORT DeleteProperty_NoThrow : Method<Throws::No> + { + static Bool call(ExecutionEngine *, const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT DeleteProperty : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, Function *, const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT DeleteName_NoThrow : Method<Throws::No> + { + static Bool call(ExecutionEngine *, int); + }; + struct Q_QML_PRIVATE_EXPORT DeleteName : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, Function *, int); + }; - static uint runtimeMethodOffset(RuntimeMethods method) { return method*QT_POINTER_SIZE; } + /* exceptions & scopes */ + struct Q_QML_PRIVATE_EXPORT ThrowException : Method<Throws::Yes> + { + static void call(ExecutionEngine *, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT PushCallContext : Method<Throws::No, ChangesContext::Yes> + { + static void call(CppStackFrame *); + }; + struct Q_QML_PRIVATE_EXPORT PushWithContext : Method<Throws::Yes, ChangesContext::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT PushCatchContext : Method<Throws::No, ChangesContext::Yes> + { + static void call(ExecutionEngine *, int, int); + }; + struct Q_QML_PRIVATE_EXPORT PushBlockContext : Method<Throws::No, ChangesContext::Yes> + { + static void call(ExecutionEngine *, int); + }; + struct Q_QML_PRIVATE_EXPORT CloneBlockContext : Method<Throws::No, ChangesContext::Yes> + { + static void call(ExecutionEngine *); + }; + struct Q_QML_PRIVATE_EXPORT PushScriptContext : Method<Throws::No, ChangesContext::Yes> + { + static void call(ExecutionEngine *, int); + }; + struct Q_QML_PRIVATE_EXPORT PopScriptContext : Method<Throws::No, ChangesContext::Yes> + { + static void call(ExecutionEngine *); + }; + struct Q_QML_PRIVATE_EXPORT ThrowReferenceError : Method<Throws::Yes> + { + static void call(ExecutionEngine *, int); + }; + struct Q_QML_PRIVATE_EXPORT ThrowOnNullOrUndefined : Method<Throws::Yes> + { + static void call(ExecutionEngine *, const Value &); + }; -#define RUNTIME_METHOD(returnvalue, name, args) \ - typedef returnvalue (*Method_##name)args; \ - enum { Method_##name##_NeedsExceptionCheck = ExceptionCheck<Method_##name>::NeedsCheck }; \ - static returnvalue method_##name args; - FOR_EACH_RUNTIME_METHOD(RUNTIME_METHOD) -#undef RUNTIME_METHOD + /* closures */ + struct Q_QML_PRIVATE_EXPORT Closure : Method<Throws::No> + { + static ReturnedValue call(ExecutionEngine *, int); + }; + + /* Function header */ + struct Q_QML_PRIVATE_EXPORT ConvertThisToObject : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT DeclareVar : Method<Throws::Yes> + { + static void call(ExecutionEngine *, Bool, int); + }; + struct Q_QML_PRIVATE_EXPORT CreateMappedArgumentsObject : Method<Throws::No> + { + static ReturnedValue call(ExecutionEngine *); + }; + struct Q_QML_PRIVATE_EXPORT CreateUnmappedArgumentsObject : Method<Throws::No> + { + static ReturnedValue call(ExecutionEngine *); + }; + struct Q_QML_PRIVATE_EXPORT CreateRestParameter : PureMethod + { + static ReturnedValue call(ExecutionEngine *, int); + }; + + /* literals */ + struct Q_QML_PRIVATE_EXPORT ArrayLiteral : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, Value[], uint); + }; + struct Q_QML_PRIVATE_EXPORT ObjectLiteral : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, int, Value[], int); + }; + struct Q_QML_PRIVATE_EXPORT CreateClass : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, int, const Value &, Value[]); + }; + + /* for-in, for-of and array destructuring */ + struct Q_QML_PRIVATE_EXPORT GetIterator : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, int); + }; + struct Q_QML_PRIVATE_EXPORT IteratorNext : IteratorMethod + { + static ReturnedValue call(ExecutionEngine *, const Value &, Value *); + }; + struct Q_QML_PRIVATE_EXPORT IteratorNextForYieldStar : IteratorMethod + { + static ReturnedValue call(ExecutionEngine *, const Value &, const Value &, Value *); + }; + struct Q_QML_PRIVATE_EXPORT IteratorClose : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT DestructureRestElement : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &); + }; + + /* conversions */ + struct Q_QML_PRIVATE_EXPORT ToObject : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT ToBoolean : PureMethod + { + static Bool call(const Value &); + }; + struct Q_QML_PRIVATE_EXPORT ToNumber : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &); + }; + /* unary operators */ + struct Q_QML_PRIVATE_EXPORT UMinus : Method<Throws::Yes> + { + static ReturnedValue call(const Value &); + }; + + /* binary operators */ + struct Q_QML_PRIVATE_EXPORT Instanceof : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT In : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT Add : Method<Throws::Yes> + { + static ReturnedValue call(ExecutionEngine *, const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT Sub : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT Mul : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT Div : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT Mod : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT Exp : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT BitAnd : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT BitOr : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT BitXor : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT Shl : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT Shr : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT UShr : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT GreaterThan : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT LessThan : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT GreaterEqual : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT LessEqual : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT Equal : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT NotEqual : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT StrictEqual : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT StrictNotEqual : Method<Throws::Yes> + { + static ReturnedValue call(const Value &, const Value &); + }; + + /* comparisons */ + struct Q_QML_PRIVATE_EXPORT CompareGreaterThan : Method<Throws::Yes> + { + static Bool call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT CompareLessThan : Method<Throws::Yes> + { + static Bool call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT CompareGreaterEqual : Method<Throws::Yes> + { + static Bool call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT CompareLessEqual : Method<Throws::Yes> + { + static Bool call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT CompareEqual : Method<Throws::Yes> + { + static Bool call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT CompareNotEqual : Method<Throws::Yes> + { + static Bool call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT CompareStrictEqual : Method<Throws::Yes> + { + static Bool call(const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT CompareStrictNotEqual : Method<Throws::Yes> + { + static Bool call(const Value &, const Value &); + }; + + struct Q_QML_PRIVATE_EXPORT CompareInstanceof : Method<Throws::Yes> + { + static Bool call(ExecutionEngine *, const Value &, const Value &); + }; + struct Q_QML_PRIVATE_EXPORT CompareIn : Method<Throws::Yes> + { + static Bool call(ExecutionEngine *, const Value &, const Value &); + }; + + struct Q_QML_PRIVATE_EXPORT RegexpLiteral : PureMethod + { + static ReturnedValue call(ExecutionEngine *, int); + }; + struct Q_QML_PRIVATE_EXPORT GetTemplateObject : PureMethod + { + static ReturnedValue call(Function *, int); + }; struct StackOffsets { static const int tailCall_function = -1; @@ -234,7 +511,6 @@ struct Q_QML_PRIVATE_EXPORT Runtime { }; static_assert(std::is_standard_layout<Runtime>::value, "Runtime needs to be standard layout in order for us to be able to use offsetof"); -static_assert(offsetof(Runtime, runtimeMethods) == 0, "JIT expects this to be the first member"); static_assert(sizeof(Runtime::BinaryOperation) == sizeof(void*), "JIT expects a function pointer to fit into a regular pointer, for cross-compilation offset translation"); } // namespace QV4 diff --git a/src/qml/jsruntime/qv4serialize.cpp b/src/qml/jsruntime/qv4serialize.cpp index 50871a4d87..a84521e205 100644 --- a/src/qml/jsruntime/qv4serialize.cpp +++ b/src/qml/jsruntime/qv4serialize.cpp @@ -39,7 +39,6 @@ #include "qv4serialize_p.h" -#include <private/qv8engine_p.h> #if QT_CONFIG(qml_list_model) #include <private/qqmllistmodel_p.h> #include <private/qqmllistmodelworkeragent_p.h> @@ -375,7 +374,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<Object>()->defineReadonlyProperty(s, v); diff --git a/src/qml/jsruntime/qv4setobject.cpp b/src/qml/jsruntime/qv4setobject.cpp index 088ecbe30d..1664d1bd71 100644 --- a/src/qml/jsruntime/qv4setobject.cpp +++ b/src/qml/jsruntime/qv4setobject.cpp @@ -76,7 +76,7 @@ ReturnedValue WeakSetCtor::construct(const FunctionObject *f, const Value *argv, ScopedFunctionObject adder(scope, a->get(ScopedString(scope, scope.engine->newString(QString::fromLatin1("add"))))); if (!adder) return scope.engine->throwTypeError(); - ScopedObject iter(scope, Runtime::method_getIterator(scope.engine, iterable, true)); + ScopedObject iter(scope, Runtime::GetIterator::call(scope.engine, iterable, true)); CHECK_EXCEPTION(); if (!iter) return a.asReturnedValue(); @@ -84,7 +84,7 @@ ReturnedValue WeakSetCtor::construct(const FunctionObject *f, const Value *argv, Value *nextValue = scope.alloc(1); ScopedValue done(scope); forever { - done = Runtime::method_iteratorNext(scope.engine, iter, nextValue); + done = Runtime::IteratorNext::call(scope.engine, iter, nextValue); CHECK_EXCEPTION(); if (done->toBoolean()) return a.asReturnedValue(); @@ -92,7 +92,7 @@ ReturnedValue WeakSetCtor::construct(const FunctionObject *f, const Value *argv, adder->call(a, nextValue, 1); if (scope.engine->hasException) { ScopedValue falsey(scope, Encode(false)); - return Runtime::method_iteratorClose(scope.engine, iter, falsey); + return Runtime::IteratorClose::call(scope.engine, iter, falsey); } } } diff --git a/src/qml/jsruntime/qv4typedarray.cpp b/src/qml/jsruntime/qv4typedarray.cpp index faf7934c06..d83f021450 100644 --- a/src/qml/jsruntime/qv4typedarray.cpp +++ b/src/qml/jsruntime/qv4typedarray.cpp @@ -1595,7 +1595,7 @@ ReturnedValue IntrinsicTypedArrayPrototype::method_toLocaleString(const Function R += separator; v = instance->get(k); - v = Runtime::method_callElement(scope.engine, v, *scope.engine->id_toLocaleString(), nullptr, 0); + v = Runtime::CallElement::call(scope.engine, v, *scope.engine->id_toLocaleString(), nullptr, 0); s = v->toString(scope.engine); if (scope.hasException()) return Encode::undefined(); diff --git a/src/qml/jsruntime/qv4value_p.h b/src/qml/jsruntime/qv4value_p.h index b4a045edfb..da08841026 100644 --- a/src/qml/jsruntime/qv4value_p.h +++ b/src/qml/jsruntime/qv4value_p.h @@ -281,8 +281,22 @@ struct Q_QML_PRIVATE_EXPORT Value inline bool isUndefined() const { return _val == 0; } inline bool isDouble() const { return (_val >> IsDouble_Shift); } - inline bool isManaged() const { return _val && ((_val >> IsManagedOrUndefined_Shift) == 0); } - inline bool isManagedOrUndefined() const { return ((_val >> IsManagedOrUndefined_Shift) == 0); } + inline bool isManaged() const + { +#if QT_POINTER_SIZE == 4 + return value() && tag() == Managed_Type_Internal; +#else + return _val && ((_val >> IsManagedOrUndefined_Shift) == 0); +#endif + } + inline bool isManagedOrUndefined() const + { +#if QT_POINTER_SIZE == 4 + return tag() == Managed_Type_Internal; +#else + return ((_val >> IsManagedOrUndefined_Shift) == 0); +#endif + } inline bool isIntOrBool() const { return (_val >> IsIntegerOrBool_Shift) == 3; 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 <private/qqmlvaluetypewrapper_p.h> -#include <private/qv8engine_p.h> #include <private/qv4qobjectwrapper_p.h> QT_BEGIN_NAMESPACE diff --git a/src/qml/jsruntime/qv4vme_moth.cpp b/src/qml/jsruntime/qv4vme_moth.cpp index dd712a15d6..98e4f4f7b9 100644 --- a/src/qml/jsruntime/qv4vme_moth.cpp +++ b/src/qml/jsruntime/qv4vme_moth.cpp @@ -65,6 +65,8 @@ #undef COUNT_INSTRUCTIONS +enum { ShowWhenDeoptimiationHappens = 0 }; + extern "C" { // This is the interface to Qt Creator's (new) QML debugger. @@ -386,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) @@ -488,13 +502,17 @@ ReturnedValue VME::exec(CppStackFrame *frame, ExecutionEngine *engine) #ifdef V4_ENABLE_JIT if (debugger == nullptr) { if (function->jittedCode == nullptr) { - if (engine->canJIT(function)) - QV4::JIT::BaselineJIT(function).generate(); - else + if (engine->canJIT(function)) { +#if QT_CONFIG(qml_tracing) + if (function->tracingEnabled()) + runTracingJit(function); + else +#endif + QV4::JIT::BaselineJIT(function).generate(); + } else { ++function->interpreterCallCount; + } } - if (function->jittedCode != nullptr) - return function->jittedCode(frame, engine); } #endif // V4_ENABLE_JIT @@ -502,7 +520,29 @@ ReturnedValue VME::exec(CppStackFrame *frame, ExecutionEngine *engine) if (debugger) debugger->enteringFunction(); - ReturnedValue result = interpret(frame, engine, function->codeData); + ReturnedValue result; + if (function->jittedCode != nullptr && debugger == nullptr) { + result = function->jittedCode(frame, engine); + if (QV4::Value::fromReturnedValue(result).isEmpty()) { // de-optimize! + if (ShowWhenDeoptimiationHappens) { + // This is debug code, which is disabled by default, and completely removed by the + // compiler. + fprintf(stderr, "*********************** DEOPT! %s ***********************\n" + "*** deopt IP: %d, line: %d\n", + function->name()->toQString().toUtf8().constData(), + frame->instructionPointer, + frame->lineNumber()); + } + delete function->codeRef; + function->codeRef = nullptr; + function->jittedCode = nullptr; + function->interpreterCallCount = 0; // reset to restart tracing: apparently we didn't have enough info before + result = interpret(frame, engine, function->codeData + frame->instructionPointer); + } + } else { + // interpreter + result = interpret(frame, engine, function->codeData); + } if (debugger) debugger->leavingFunction(result); @@ -607,16 +647,16 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, MOTH_END_INSTR(LoadRuntimeString) MOTH_BEGIN_INSTR(MoveRegExp) - STACK_VALUE(destReg) = Runtime::method_regexpLiteral(engine, regExpId); + STACK_VALUE(destReg) = Runtime::RegexpLiteral::call(engine, regExpId); MOTH_END_INSTR(MoveRegExp) MOTH_BEGIN_INSTR(LoadClosure) - acc = Runtime::method_closure(engine, value); + acc = Runtime::Closure::call(engine, value); MOTH_END_INSTR(LoadClosure) MOTH_BEGIN_INSTR(LoadName) STORE_IP(); - acc = Runtime::method_loadName(engine, name); + acc = Runtime::LoadName::call(engine, name); CHECK_EXCEPTION; traceValue(acc, function, traceSlot); MOTH_END_INSTR(LoadName) @@ -640,14 +680,14 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, MOTH_BEGIN_INSTR(StoreNameStrict) STORE_IP(); STORE_ACC(); - Runtime::method_storeNameStrict(engine, name, accumulator); + Runtime::StoreNameStrict::call(engine, name, accumulator); CHECK_EXCEPTION; MOTH_END_INSTR(StoreNameStrict) MOTH_BEGIN_INSTR(StoreNameSloppy) STORE_IP(); STORE_ACC(); - Runtime::method_storeNameSloppy(engine, name, accumulator); + Runtime::StoreNameSloppy::call(engine, name, accumulator); CHECK_EXCEPTION; MOTH_END_INSTR(StoreNameSloppy) @@ -655,11 +695,11 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, STORE_IP(); STORE_ACC(); #if QT_CONFIG(qml_tracing) - acc = Runtime::method_loadElement_traced(engine, STACK_VALUE(base), accumulator, function->traceInfo(traceSlot)); + acc = Runtime::LoadElement_Traced::call(engine, STACK_VALUE(base), accumulator, function->traceInfo(traceSlot)); traceValue(acc, function, traceSlot); #else Q_UNUSED(traceSlot); - acc = Runtime::method_loadElement(engine, STACK_VALUE(base), accumulator); + acc = Runtime::LoadElement::call(engine, STACK_VALUE(base), accumulator); #endif CHECK_EXCEPTION; MOTH_END_INSTR(LoadElement) @@ -668,10 +708,10 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, STORE_IP(); STORE_ACC(); #if QT_CONFIG(qml_tracing) - Runtime::method_storeElement_traced(engine, STACK_VALUE(base), STACK_VALUE(index), accumulator, function->traceInfo(traceSlot)); + Runtime::StoreElement_traced::call(engine, STACK_VALUE(base), STACK_VALUE(index), accumulator, function->traceInfo(traceSlot)); #else Q_UNUSED(traceSlot); - Runtime::method_storeElement(engine, STACK_VALUE(base), STACK_VALUE(index), accumulator); + Runtime::StoreElement::call(engine, STACK_VALUE(base), STACK_VALUE(index), accumulator); #endif CHECK_EXCEPTION; MOTH_END_INSTR(StoreElement) @@ -679,7 +719,7 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, MOTH_BEGIN_INSTR(LoadProperty) STORE_IP(); STORE_ACC(); - acc = Runtime::method_loadProperty(engine, accumulator, name); + acc = Runtime::LoadProperty::call(engine, accumulator, name); CHECK_EXCEPTION; traceValue(acc, function, traceSlot); MOTH_END_INSTR(LoadProperty) @@ -706,7 +746,7 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, MOTH_BEGIN_INSTR(StoreProperty) STORE_IP(); STORE_ACC(); - Runtime::method_storeProperty(engine, STACK_VALUE(base), name, accumulator); + Runtime::StoreProperty::call(engine, STACK_VALUE(base), name, accumulator); CHECK_EXCEPTION; MOTH_END_INSTR(StoreProperty) @@ -722,14 +762,14 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, MOTH_BEGIN_INSTR(LoadSuperProperty) STORE_IP(); STORE_ACC(); - acc = Runtime::method_loadSuperProperty(engine, STACK_VALUE(property)); + acc = Runtime::LoadSuperProperty::call(engine, STACK_VALUE(property)); CHECK_EXCEPTION; MOTH_END_INSTR(LoadSuperProperty) MOTH_BEGIN_INSTR(StoreSuperProperty) STORE_IP(); STORE_ACC(); - Runtime::method_storeSuperProperty(engine, STACK_VALUE(property), accumulator); + Runtime::StoreSuperProperty::call(engine, STACK_VALUE(property), accumulator); CHECK_EXCEPTION; MOTH_END_INSTR(StoreSuperProperty) @@ -760,7 +800,7 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, MOTH_BEGIN_INSTR(IteratorNextForYieldStar) STORE_ACC(); - acc = Runtime::method_iteratorNextForYieldStar(engine, accumulator, STACK_VALUE(iterator), &STACK_VALUE(object)); + acc = Runtime::IteratorNextForYieldStar::call(engine, accumulator, STACK_VALUE(iterator), &STACK_VALUE(object)); CHECK_EXCEPTION; MOTH_END_INSTR(IteratorNextForYieldStar) @@ -791,7 +831,7 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, MOTH_BEGIN_INSTR(CallProperty) STORE_IP(); - acc = Runtime::method_callProperty(engine, stack + base, name, stack + argv, argc); + acc = Runtime::CallProperty::call(engine, stack[base], name, stack + argv, argc); CHECK_EXCEPTION; traceValue(acc, function, traceSlot); MOTH_END_INSTR(CallProperty) @@ -826,42 +866,42 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, MOTH_BEGIN_INSTR(CallElement) STORE_IP(); - acc = Runtime::method_callElement(engine, stack + base, STACK_VALUE(index), stack + argv, argc); + acc = Runtime::CallElement::call(engine, stack[base], STACK_VALUE(index), stack + argv, argc); CHECK_EXCEPTION; traceValue(acc, function, traceSlot); MOTH_END_INSTR(CallElement) MOTH_BEGIN_INSTR(CallName) STORE_IP(); - acc = Runtime::method_callName(engine, name, stack + argv, argc); + acc = Runtime::CallName::call(engine, name, stack + argv, argc); CHECK_EXCEPTION; traceValue(acc, function, traceSlot); MOTH_END_INSTR(CallName) MOTH_BEGIN_INSTR(CallPossiblyDirectEval) STORE_IP(); - acc = Runtime::method_callPossiblyDirectEval(engine, stack + argv, argc); + acc = Runtime::CallPossiblyDirectEval::call(engine, stack + argv, argc); CHECK_EXCEPTION; traceValue(acc, function, traceSlot); MOTH_END_INSTR(CallPossiblyDirectEval) MOTH_BEGIN_INSTR(CallGlobalLookup) STORE_IP(); - acc = Runtime::method_callGlobalLookup(engine, index, stack + argv, argc); + acc = Runtime::CallGlobalLookup::call(engine, index, stack + argv, argc); CHECK_EXCEPTION; traceValue(acc, function, traceSlot); MOTH_END_INSTR(CallGlobalLookup) MOTH_BEGIN_INSTR(CallQmlContextPropertyLookup) STORE_IP(); - acc = Runtime::method_callQmlContextPropertyLookup(engine, index, stack + argv, argc); + acc = Runtime::CallQmlContextPropertyLookup::call(engine, index, stack + argv, argc); CHECK_EXCEPTION; traceValue(acc, function, traceSlot); MOTH_END_INSTR(CallQmlContextPropertyLookup) MOTH_BEGIN_INSTR(CallWithSpread) STORE_IP(); - acc = Runtime::method_callWithSpread(engine, STACK_VALUE(func), STACK_VALUE(thisObject), stack + argv, argc); + acc = Runtime::CallWithSpread::call(engine, STACK_VALUE(func), STACK_VALUE(thisObject), stack + argv, argc); CHECK_EXCEPTION; traceValue(acc, function, traceSlot); MOTH_END_INSTR(CallWithSpread) @@ -872,19 +912,19 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, *engine->jsAlloca(1) = Primitive::fromInt32(argv); *engine->jsAlloca(1) = STACK_VALUE(thisObject); *engine->jsAlloca(1) = STACK_VALUE(func); - return Runtime::method_tailCall(frame, engine); + return Runtime::TailCall::call(frame, engine); CHECK_EXCEPTION; MOTH_END_INSTR(TailCall) MOTH_BEGIN_INSTR(Construct) STORE_IP(); - acc = Runtime::method_construct(engine, STACK_VALUE(func), ACC, stack + argv, argc); + acc = Runtime::Construct::call(engine, STACK_VALUE(func), ACC, stack + argv, argc); CHECK_EXCEPTION; MOTH_END_INSTR(Construct) MOTH_BEGIN_INSTR(ConstructWithSpread) STORE_IP(); - acc = Runtime::method_constructWithSpread(engine, STACK_VALUE(func), ACC, stack + argv, argc); + acc = Runtime::ConstructWithSpread::call(engine, STACK_VALUE(func), ACC, stack + argv, argc); CHECK_EXCEPTION; MOTH_END_INSTR(ConstructWithSpread) @@ -912,7 +952,7 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, if (ACC.isEmpty()) { STORE_IP(); STORE_ACC(); - Runtime::method_throwReferenceError(engine, name); + Runtime::ThrowReferenceError::call(engine, name); goto handleUnwind; } MOTH_END_INSTR(DeadTemporalZoneCheck) @@ -920,7 +960,7 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, MOTH_BEGIN_INSTR(ThrowException) STORE_IP(); STORE_ACC(); - Runtime::method_throwException(engine, accumulator); + Runtime::ThrowException::call(engine, accumulator); goto handleUnwind; MOTH_END_INSTR(ThrowException) @@ -938,40 +978,36 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, MOTH_END_INSTR(SetException) MOTH_BEGIN_INSTR(PushCatchContext) - ExecutionContext *c = static_cast<ExecutionContext *>(stack + CallData::Context); - STACK_VALUE(CallData::Context) = Runtime::method_createCatchContext(c, index, name); + Runtime::PushCatchContext::call(engine, index, name); MOTH_END_INSTR(PushCatchContext) MOTH_BEGIN_INSTR(CreateCallContext) - stack[CallData::Context] = ExecutionContext::newCallContext(frame); + Runtime::PushCallContext::call(frame); MOTH_END_INSTR(CreateCallContext) MOTH_BEGIN_INSTR(PushWithContext) STORE_IP(); STORE_ACC(); - auto ctx = Runtime::method_createWithContext(engine, stack); + acc = Runtime::PushWithContext::call(engine, stack[CallData::Accumulator]); CHECK_EXCEPTION; - STACK_VALUE(CallData::Context) = ctx; MOTH_END_INSTR(PushWithContext) MOTH_BEGIN_INSTR(PushBlockContext) STORE_ACC(); - ExecutionContext *c = static_cast<ExecutionContext *>(stack + CallData::Context); - STACK_VALUE(CallData::Context) = Runtime::method_createBlockContext(c, index); + Runtime::PushBlockContext::call(engine, index); MOTH_END_INSTR(PushBlockContext) MOTH_BEGIN_INSTR(CloneBlockContext) STORE_ACC(); - ExecutionContext *c = static_cast<ExecutionContext *>(stack + CallData::Context); - STACK_VALUE(CallData::Context) = Runtime::method_cloneBlockContext(c); + Runtime::CloneBlockContext::call(engine); MOTH_END_INSTR(CloneBlockContext) MOTH_BEGIN_INSTR(PushScriptContext) - STACK_VALUE(CallData::Context) = Runtime::method_createScriptContext(engine, index); + Runtime::PushScriptContext::call(engine, index); MOTH_END_INSTR(PushScriptContext) MOTH_BEGIN_INSTR(PopScriptContext) - STACK_VALUE(CallData::Context) = Runtime::method_popScriptContext(engine); + Runtime::PopScriptContext::call(engine); MOTH_END_INSTR(PopScriptContext) MOTH_BEGIN_INSTR(PopContext) @@ -982,14 +1018,14 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, MOTH_BEGIN_INSTR(GetIterator) STORE_IP(); STORE_ACC(); - acc = Runtime::method_getIterator(engine, accumulator, iterator); + acc = Runtime::GetIterator::call(engine, accumulator, iterator); CHECK_EXCEPTION; MOTH_END_INSTR(GetIterator) MOTH_BEGIN_INSTR(IteratorNext) STORE_IP(); STORE_ACC(); - acc = Runtime::method_iteratorNext(engine, accumulator, &STACK_VALUE(value)); + acc = Runtime::IteratorNext::call(engine, accumulator, &STACK_VALUE(value)); STACK_VALUE(done) = acc; CHECK_EXCEPTION; MOTH_END_INSTR(IteratorNext) @@ -997,97 +1033,73 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, MOTH_BEGIN_INSTR(IteratorClose) STORE_IP(); STORE_ACC(); - acc = Runtime::method_iteratorClose(engine, accumulator, STACK_VALUE(done)); + acc = Runtime::IteratorClose::call(engine, accumulator, STACK_VALUE(done)); CHECK_EXCEPTION; MOTH_END_INSTR(IteratorClose) MOTH_BEGIN_INSTR(DestructureRestElement) STORE_IP(); STORE_ACC(); - acc = Runtime::method_destructureRestElement(engine, ACC); + acc = Runtime::DestructureRestElement::call(engine, ACC); CHECK_EXCEPTION; MOTH_END_INSTR(DestructureRestElement) MOTH_BEGIN_INSTR(DeleteProperty) - if (!Runtime::method_deleteProperty(engine, STACK_VALUE(base), STACK_VALUE(index))) { - if (function->isStrict()) { - STORE_IP(); - engine->throwTypeError(); - goto handleUnwind; - } - acc = Encode(false); - } else { - acc = Encode(true); - } + acc = Runtime::DeleteProperty::call(engine, function, STACK_VALUE(base), STACK_VALUE(index)); + CHECK_EXCEPTION; MOTH_END_INSTR(DeleteProperty) MOTH_BEGIN_INSTR(DeleteName) - if (!Runtime::method_deleteName(engine, name)) { - if (function->isStrict()) { - STORE_IP(); - QString n = function->compilationUnit->runtimeStrings[name]->toQString(); - engine->throwSyntaxError(QStringLiteral("Can't delete property %1").arg(n)); - goto handleUnwind; - } - acc = Encode(false); - } else { - acc = Encode(true); - } + acc = Runtime::DeleteName::call(engine, function, name); + CHECK_EXCEPTION; MOTH_END_INSTR(DeleteName) MOTH_BEGIN_INSTR(TypeofName) - acc = Runtime::method_typeofName(engine, name); + acc = Runtime::TypeofName::call(engine, name); MOTH_END_INSTR(TypeofName) MOTH_BEGIN_INSTR(TypeofValue) STORE_ACC(); - acc = Runtime::method_typeofValue(engine, accumulator); + acc = Runtime::TypeofValue::call(engine, accumulator); MOTH_END_INSTR(TypeofValue) MOTH_BEGIN_INSTR(DeclareVar) - Runtime::method_declareVar(engine, isDeletable, varName); + Runtime::DeclareVar::call(engine, isDeletable, varName); MOTH_END_INSTR(DeclareVar) MOTH_BEGIN_INSTR(DefineArray) QV4::Value *arguments = stack + args; - acc = Runtime::method_arrayLiteral(engine, arguments, argc); + acc = Runtime::ArrayLiteral::call(engine, arguments, argc); MOTH_END_INSTR(DefineArray) MOTH_BEGIN_INSTR(DefineObjectLiteral) QV4::Value *arguments = stack + args; - acc = Runtime::method_objectLiteral(engine, internalClassId, arguments, argc); + acc = Runtime::ObjectLiteral::call(engine, internalClassId, arguments, argc); MOTH_END_INSTR(DefineObjectLiteral) MOTH_BEGIN_INSTR(CreateClass) - acc = Runtime::method_createClass(engine, classIndex, STACK_VALUE(heritage), stack + computedNames); + acc = Runtime::CreateClass::call(engine, classIndex, STACK_VALUE(heritage), stack + computedNames); MOTH_END_INSTR(CreateClass) MOTH_BEGIN_INSTR(CreateMappedArgumentsObject) - acc = Runtime::method_createMappedArgumentsObject(engine); + acc = Runtime::CreateMappedArgumentsObject::call(engine); MOTH_END_INSTR(CreateMappedArgumentsObject) MOTH_BEGIN_INSTR(CreateUnmappedArgumentsObject) - acc = Runtime::method_createUnmappedArgumentsObject(engine); + acc = Runtime::CreateUnmappedArgumentsObject::call(engine); MOTH_END_INSTR(CreateUnmappedArgumentsObject) MOTH_BEGIN_INSTR(CreateRestParameter) - acc = Runtime::method_createRestParameter(engine, argIndex); + acc = Runtime::CreateRestParameter::call(engine, argIndex); MOTH_END_INSTR(CreateRestParameter) MOTH_BEGIN_INSTR(ConvertThisToObject) - Value *t = &stack[CallData::This]; - if (!t->isObject()) { - if (t->isNullOrUndefined()) { - *t = engine->globalObject->asReturnedValue(); - } else { - *t = t->toObject(engine)->asReturnedValue(); - CHECK_EXCEPTION; - } - } + stack[CallData::This] = Runtime::ConvertThisToObject::call(engine, stack[CallData::This]); + CHECK_EXCEPTION; MOTH_END_INSTR(ConvertThisToObject) MOTH_BEGIN_INSTR(LoadSuperConstructor) - acc = Runtime::method_loadSuperConstructor(engine, stack[CallData::Function]); + acc = Runtime::LoadSuperConstructor::call(engine, stack[CallData::Function]); CHECK_EXCEPTION; MOTH_END_INSTR(LoadSuperConstructor) @@ -1168,7 +1180,7 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, acc = Encode(left.int_32() == ACC.int_32()); } else { STORE_ACC(); - acc = Encode(bool(Runtime::method_compareEqual(left, accumulator))); + acc = Encode(bool(Runtime::CompareEqual::call(left, accumulator))); CHECK_EXCEPTION; } MOTH_END_INSTR(CmpEq) @@ -1179,7 +1191,7 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, acc = Encode(bool(left.int_32() != ACC.int_32())); } else { STORE_ACC(); - acc = Encode(bool(!Runtime::method_compareEqual(left, accumulator))); + acc = Encode(bool(!Runtime::CompareEqual::call(left, accumulator))); CHECK_EXCEPTION; } MOTH_END_INSTR(CmpNe) @@ -1192,7 +1204,7 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, acc = Encode(left.asDouble() > ACC.asDouble()); } else { STORE_ACC(); - acc = Encode(bool(Runtime::method_compareGreaterThan(left, accumulator))); + acc = Encode(bool(Runtime::CompareGreaterThan::call(left, accumulator))); CHECK_EXCEPTION; } MOTH_END_INSTR(CmpGt) @@ -1205,7 +1217,7 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, acc = Encode(left.asDouble() >= ACC.asDouble()); } else { STORE_ACC(); - acc = Encode(bool(Runtime::method_compareGreaterEqual(left, accumulator))); + acc = Encode(bool(Runtime::CompareGreaterEqual::call(left, accumulator))); CHECK_EXCEPTION; } MOTH_END_INSTR(CmpGe) @@ -1218,7 +1230,7 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, acc = Encode(left.asDouble() < ACC.asDouble()); } else { STORE_ACC(); - acc = Encode(bool(Runtime::method_compareLessThan(left, accumulator))); + acc = Encode(bool(Runtime::CompareLessThan::call(left, accumulator))); CHECK_EXCEPTION; } MOTH_END_INSTR(CmpLt) @@ -1231,7 +1243,7 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, acc = Encode(left.asDouble() <= ACC.asDouble()); } else { STORE_ACC(); - acc = Encode(bool(Runtime::method_compareLessEqual(left, accumulator))); + acc = Encode(bool(Runtime::CompareLessEqual::call(left, accumulator))); CHECK_EXCEPTION; } MOTH_END_INSTR(CmpLe) @@ -1241,7 +1253,7 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, acc = Encode(true); } else { STORE_ACC(); - acc = Encode(bool(RuntimeHelpers::strictEqual(STACK_VALUE(lhs), accumulator))); + acc = Runtime::StrictEqual::call(STACK_VALUE(lhs), accumulator); CHECK_EXCEPTION; } MOTH_END_INSTR(CmpStrictEqual) @@ -1249,7 +1261,7 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, MOTH_BEGIN_INSTR(CmpStrictNotEqual) if (STACK_VALUE(lhs).rawValue() != ACC.rawValue() || ACC.isNaN()) { STORE_ACC(); - acc = Encode(!RuntimeHelpers::strictEqual(STACK_VALUE(lhs), accumulator)); + acc = Runtime::StrictNotEqual::call(STACK_VALUE(lhs), accumulator); CHECK_EXCEPTION; } else { acc = Encode(false); @@ -1258,13 +1270,13 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, MOTH_BEGIN_INSTR(CmpIn) STORE_ACC(); - acc = Runtime::method_in(engine, STACK_VALUE(lhs), accumulator); + acc = Runtime::In::call(engine, STACK_VALUE(lhs), accumulator); CHECK_EXCEPTION; MOTH_END_INSTR(CmpIn) MOTH_BEGIN_INSTR(CmpInstanceOf) STORE_ACC(); - acc = Runtime::method_instanceof(engine, STACK_VALUE(lhs), ACC); + acc = Runtime::Instanceof::call(engine, STACK_VALUE(lhs), ACC); CHECK_EXCEPTION; MOTH_END_INSTR(CmpInstanceOf) @@ -1277,7 +1289,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; } @@ -1342,7 +1359,7 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, traceDoubleValue(function, traceSlot); } else { STORE_ACC(); - acc = Runtime::method_add(engine, left, accumulator); + acc = Runtime::Add::call(engine, left, accumulator); CHECK_EXCEPTION; traceOtherValue(function, traceSlot); } @@ -1357,7 +1374,7 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, traceDoubleValue(function, traceSlot); } else { STORE_ACC(); - acc = Runtime::method_sub(left, accumulator); + acc = Runtime::Sub::call(left, accumulator); CHECK_EXCEPTION; traceOtherValue(function, traceSlot); } @@ -1382,7 +1399,7 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, traceDoubleValue(function, traceSlot); } else { STORE_ACC(); - acc = Runtime::method_mul(left, accumulator); + acc = Runtime::Mul::call(left, accumulator); CHECK_EXCEPTION; traceOtherValue(function, traceSlot); } @@ -1390,13 +1407,13 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, MOTH_BEGIN_INSTR(Div) STORE_ACC(); - acc = Runtime::method_div(STACK_VALUE(lhs), accumulator); + acc = Runtime::Div::call(STACK_VALUE(lhs), accumulator); CHECK_EXCEPTION; MOTH_END_INSTR(Div) MOTH_BEGIN_INSTR(Mod) STORE_ACC(); - acc = Runtime::method_mod(STACK_VALUE(lhs), accumulator); + acc = Runtime::Mod::call(STACK_VALUE(lhs), accumulator); CHECK_EXCEPTION; traceValue(acc, function, traceSlot); MOTH_END_INSTR(Mod) @@ -1485,7 +1502,7 @@ QV4::ReturnedValue VME::interpret(CppStackFrame *frame, ExecutionEngine *engine, MOTH_END_INSTR(ThrowOnNullOrUndefined) MOTH_BEGIN_INSTR(GetTemplateObject) - acc = RuntimeHelpers::getTemplateObject(function, index); + acc = Runtime::GetTemplateObject::call(function, index); MOTH_END_INSTR(GetTemplateObject) MOTH_BEGIN_INSTR(Debug) diff --git a/src/qml/jsruntime/qv4vme_moth_p.h b/src/qml/jsruntime/qv4vme_moth_p.h index 8a76e60f20..4ac7120d36 100644 --- a/src/qml/jsruntime/qv4vme_moth_p.h +++ b/src/qml/jsruntime/qv4vme_moth_p.h @@ -58,6 +58,8 @@ QT_BEGIN_NAMESPACE namespace QV4 { namespace Moth { +void runTracingJit(QV4::Function *function); + class VME { public: diff --git a/src/qml/jsruntime/qv4vtable_p.h b/src/qml/jsruntime/qv4vtable_p.h index aff1ae82d7..a4d91640c5 100644 --- a/src/qml/jsruntime/qv4vtable_p.h +++ b/src/qml/jsruntime/qv4vtable_p.h @@ -90,8 +90,8 @@ struct VTable typedef bool (*ResolveLookupSetter)(Object *, ExecutionEngine *, Lookup *, const Value &); const VTable * const parent; - quint32 inlinePropertyOffset : 16; - quint32 nInlineProperties : 16; + quint16 inlinePropertyOffset; + quint16 nInlineProperties; quint8 isExecutionContext; quint8 isString; quint8 isObject; diff --git a/src/qml/memory/qv4mm.cpp b/src/qml/memory/qv4mm.cpp index cac68bdcaf..34d334a24d 100644 --- a/src/qml/memory/qv4mm.cpp +++ b/src/qml/memory/qv4mm.cpp @@ -999,12 +999,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]; @@ -1013,7 +1013,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"; @@ -1023,7 +1023,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; } @@ -1064,7 +1064,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(); @@ -1082,7 +1083,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."; @@ -1104,7 +1106,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) { @@ -1126,7 +1129,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; 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 diff --git a/src/qml/qml/ftw/ftw.pri b/src/qml/qml/ftw/ftw.pri index ade05a596b..0bb8cb954e 100644 --- a/src/qml/qml/ftw/ftw.pri +++ b/src/qml/qml/ftw/ftw.pri @@ -11,12 +11,15 @@ HEADERS += \ $$PWD/qrecyclepool_p.h \ $$PWD/qflagpointer_p.h \ $$PWD/qlazilyallocated_p.h \ - $$PWD/qqmlnullablevalue_p.h + $$PWD/qqmlnullablevalue_p.h \ + $$PWD/qstringhash_p.h \ + $$PWD/qlinkedstringhash_p.h SOURCES += \ $$PWD/qintrusivelist.cpp \ $$PWD/qhashedstring.cpp \ $$PWD/qqmlthread.cpp \ + $$PWD/qstringhash.cpp # mirrors logic in $$QT_SOURCE_TREE/config.tests/unix/clock-gettime/clock-gettime.pri # clock_gettime() is implemented in librt on these systems diff --git a/src/qml/qml/ftw/qhashedstring.cpp b/src/qml/qml/ftw/qhashedstring.cpp index 117670dbfc..bb6688599d 100644 --- a/src/qml/qml/ftw/qhashedstring.cpp +++ b/src/qml/qml/ftw/qhashedstring.cpp @@ -39,136 +39,7 @@ #include "qhashedstring_p.h" - - -/* - A QHash has initially around pow(2, MinNumBits) buckets. For - example, if MinNumBits is 4, it has 17 buckets. -*/ -const int MinNumBits = 4; - -/* - The prime_deltas array is a table of selected prime values, even - though it doesn't look like one. The primes we are using are 1, - 2, 5, 11, 17, 37, 67, 131, 257, ..., i.e. primes in the immediate - surrounding of a power of two. - - The primeForNumBits() function returns the prime associated to a - power of two. For example, primeForNumBits(8) returns 257. -*/ - -static const uchar prime_deltas[] = { - 0, 0, 1, 3, 1, 5, 3, 3, 1, 9, 7, 5, 3, 9, 25, 3, - 1, 21, 3, 21, 7, 15, 9, 5, 3, 29, 15, 0, 0, 0, 0, 0 -}; - -static inline int primeForNumBits(int numBits) -{ - return (1 << numBits) + prime_deltas[numBits]; -} - -void QStringHashData::rehashToSize(int size) -{ - short bits = qMax(MinNumBits, (int)numBits); - while (primeForNumBits(bits) < size) bits++; - - if (bits > numBits) - rehashToBits(bits); -} - -void QStringHashData::rehashToBits(short bits) -{ - numBits = qMax(MinNumBits, (int)bits); - - int nb = primeForNumBits(numBits); - if (nb == numBuckets && buckets) - return; - -#ifdef QSTRINGHASH_LINK_DEBUG - if (linkCount) - qFatal("QStringHash: Illegal attempt to rehash a linked hash."); -#endif - - QStringHashNode **newBuckets = new QStringHashNode *[nb]; - ::memset(newBuckets, 0, sizeof(QStringHashNode *) * nb); - - // Preserve the existing order within buckets so that items with the - // same key will retain the same find/findNext order - for (int i = 0; i < numBuckets; ++i) { - QStringHashNode *bucket = buckets[i]; - if (bucket) - rehashNode(newBuckets, nb, bucket); - } - - delete [] buckets; - buckets = newBuckets; - numBuckets = nb; -} - -void QStringHashData::rehashNode(QStringHashNode **newBuckets, int nb, QStringHashNode *node) -{ - QStringHashNode *next = node->next.data(); - if (next) - rehashNode(newBuckets, nb, next); - - int bucket = node->hash % nb; - node->next = newBuckets[bucket]; - newBuckets[bucket] = node; -} - -// Copy of QString's qMemCompare -bool QHashedString::compare(const QChar *lhs, const QChar *rhs, int length) -{ - Q_ASSERT(lhs && rhs); - const quint16 *a = (const quint16 *)lhs; - const quint16 *b = (const quint16 *)rhs; - - if (a == b || !length) - return true; - - union { - const quint16 *w; - const quint32 *d; - quintptr value; - } sa, sb; - sa.w = a; - sb.w = b; - - // check alignment - if ((sa.value & 2) == (sb.value & 2)) { - // both addresses have the same alignment - if (sa.value & 2) { - // both addresses are not aligned to 4-bytes boundaries - // compare the first character - if (*sa.w != *sb.w) - return false; - --length; - ++sa.w; - ++sb.w; - - // now both addresses are 4-bytes aligned - } - - // both addresses are 4-bytes aligned - // do a fast 32-bit comparison - const quint32 *e = sa.d + (length >> 1); - for ( ; sa.d != e; ++sa.d, ++sb.d) { - if (*sa.d != *sb.d) - return false; - } - - // do we have a tail? - return (length & 1) ? *sa.w == *sb.w : true; - } else { - // one of the addresses isn't 4-byte aligned but the other is - const quint16 *e = sa.w + length; - for ( ; sa.w != e; ++sa.w, ++sb.w) { - if (*sa.w != *sb.w) - return false; - } - } - return true; -} +QT_BEGIN_NAMESPACE QHashedStringRef QHashedStringRef::mid(int offset, int length) const { @@ -228,3 +99,4 @@ QString QHashedCStringRef::toUtf16() const return rv; } +QT_END_NAMESPACE diff --git a/src/qml/qml/ftw/qhashedstring_p.h b/src/qml/qml/ftw/qhashedstring_p.h index 2d6c25bdd3..b9f3f81219 100644 --- a/src/qml/qml/ftw/qhashedstring_p.h +++ b/src/qml/qml/ftw/qhashedstring_p.h @@ -63,9 +63,6 @@ QT_BEGIN_NAMESPACE -// Enable this to debug hash linking assumptions. -// #define QSTRINGHASH_LINK_DEBUG - class QHashedStringRef; class Q_QML_PRIVATE_EXPORT QHashedString : public QString { @@ -174,854 +171,6 @@ private: mutable quint32 m_hash = 0; }; -class QStringHashData; -class Q_AUTOTEST_EXPORT QStringHashNode -{ -public: - QStringHashNode() - : ckey(nullptr) - { - } - - QStringHashNode(const QHashedString &key) - : length(key.length()), hash(key.hash()), symbolId(0) - { - strData = const_cast<QHashedString &>(key).data_ptr(); - setQString(true); - strData->ref.ref(); - } - - QStringHashNode(const QHashedCStringRef &key) - : length(key.length()), hash(key.hash()), symbolId(0), ckey(key.constData()) - { - } - - QStringHashNode(const QStringHashNode &o) - : length(o.length), hash(o.hash), symbolId(o.symbolId), ckey(o.ckey) - { - setQString(o.isQString()); - if (isQString()) { strData->ref.ref(); } - } - - ~QStringHashNode() - { - if (isQString()) { if (!strData->ref.deref()) free(strData); } - } - - QFlagPointer<QStringHashNode> next; - - qint32 length = 0; - quint32 hash = 0; - quint32 symbolId = 0; - - union { - const char *ckey; - QStringData *strData; - }; - - inline QHashedString key() const - { - if (isQString()) - return QHashedString(QString((QChar *)strData->data(), length), hash); - - return QHashedString(QString::fromLatin1(ckey, length), hash); - } - - bool isQString() const { return next.flag(); } - void setQString(bool v) { if (v) next.setFlag(); else next.clearFlag(); } - - inline char *cStrData() const { return (char *)ckey; } - inline quint16 *utf16Data() const { return (quint16 *)strData->data(); } - - inline bool equals(const QV4::Value &string) const { - QString s = string.toQStringNoThrow(); - if (isQString()) { - QStringDataPtr dd; - dd.ptr = strData; - strData->ref.ref(); - return QString(dd) == s; - } else { - return QLatin1String(cStrData(), length) == s; - } - } - - inline bool equals(const QV4::String *string) const { - if (length != string->d()->length() || hash != string->hashValue()) - return false; - if (isQString()) { - QStringDataPtr dd; - dd.ptr = strData; - strData->ref.ref(); - return QString(dd) == string->toQString(); - } else { - return QLatin1String(cStrData(), length) == string->toQString(); - } - } - - inline bool equals(const QHashedStringRef &string) const { - return length == string.length() && - hash == string.hash() && - (isQString()?QHashedString::compare(string.constData(), (const QChar *)utf16Data(), length): - QHashedString::compare(string.constData(), cStrData(), length)); - } - - inline bool equals(const QHashedCStringRef &string) const { - return length == string.length() && - hash == string.hash() && - (isQString()?QHashedString::compare((const QChar *)utf16Data(), string.constData(), length): - QHashedString::compare(string.constData(), cStrData(), length)); - } -}; - -class Q_AUTOTEST_EXPORT QStringHashData -{ -public: - QStringHashData() {} - - QStringHashNode **buckets = nullptr; - int numBuckets = 0; - int size = 0; - short numBits = 0; -#ifdef QSTRINGHASH_LINK_DEBUG - int linkCount = 0; -#endif - - struct IteratorData { - IteratorData() {} - QStringHashNode *n = nullptr; - void *p = nullptr; - }; - void rehashToBits(short); - void rehashToSize(int); - void rehashNode(QStringHashNode **newBuckets, int nb, QStringHashNode *node); - -private: - QStringHashData(const QStringHashData &); - QStringHashData &operator=(const QStringHashData &); -}; - -// For a supplied key type, in what form do we need to keep a hashed version? -template<typename T> -struct HashedForm {}; - -template<> struct HashedForm<QString> { typedef QHashedString Type; }; -template<> struct HashedForm<QStringRef> { typedef QHashedStringRef Type; }; -template<> struct HashedForm<QHashedString> { typedef const QHashedString &Type; }; -template<> struct HashedForm<QV4::String *> { typedef const QV4::String *Type; }; -template<> struct HashedForm<const QV4::String *> { typedef const QV4::String *Type; }; -template<> struct HashedForm<QHashedStringRef> { typedef const QHashedStringRef &Type; }; -template<> struct HashedForm<QLatin1String> { typedef QHashedCStringRef Type; }; -template<> struct HashedForm<QHashedCStringRef> { typedef const QHashedCStringRef &Type; }; - -class QStringHashBase -{ -public: - static HashedForm<QString>::Type hashedString(const QString &s) { return QHashedString(s);} - static HashedForm<QStringRef>::Type hashedString(const QStringRef &s) { return QHashedStringRef(s.constData(), s.size());} - static HashedForm<QHashedString>::Type hashedString(const QHashedString &s) { return s; } - static HashedForm<QV4::String *>::Type hashedString(QV4::String *s) { return s; } - static HashedForm<const QV4::String *>::Type hashedString(const QV4::String *s) { return s; } - static HashedForm<QHashedStringRef>::Type hashedString(const QHashedStringRef &s) { return s; } - - static HashedForm<QLatin1String>::Type hashedString(const QLatin1String &s) { return QHashedCStringRef(s.data(), s.size()); } - static HashedForm<QHashedCStringRef>::Type hashedString(const QHashedCStringRef &s) { return s; } - - static const QString &toQString(const QString &s) { return s; } - static const QString &toQString(const QHashedString &s) { return s; } - static QString toQString(const QV4::String *s) { return s->toQString(); } - static QString toQString(const QHashedStringRef &s) { return s.toString(); } - - static QString toQString(const QLatin1String &s) { return QString(s); } - static QString toQString(const QHashedCStringRef &s) { return s.toUtf16(); } - - static inline quint32 hashOf(const QHashedStringRef &s) { return s.hash(); } - static inline quint32 hashOf(QV4::String *s) { return s->hashValue(); } - static inline quint32 hashOf(const QV4::String *s) { return s->hashValue(); } - - template<typename K> - static inline quint32 hashOf(const K &key) { return hashedString(key).hash(); } -}; - -template<class T> -class QStringHash : public QStringHashBase -{ -public: - typedef QHashedString key_type; - typedef T mapped_type; - - struct Node : public QStringHashNode { - Node(const QHashedString &key, const T &value) : QStringHashNode(key), value(value) {} - Node(const QHashedCStringRef &key, const T &value) : QStringHashNode(key), value(value) {} - Node(const Node &o) : QStringHashNode(o), value(o.value) {} - Node() {} - T value; - }; - struct NewedNode : public Node { - NewedNode(const QHashedString &key, const T &value) : Node(key, value), nextNewed(nullptr) {} - NewedNode(const QHashedCStringRef &key, const T &value) : Node(key, value), nextNewed(nullptr) {} - NewedNode(const Node &o) : Node(o), nextNewed(nullptr) {} - NewedNode *nextNewed; - }; - struct ReservedNodePool - { - ReservedNodePool() : nodes(nullptr) {} - ~ReservedNodePool() { delete [] nodes; } - int count = 0; - int used = 0; - Node *nodes; - }; - - QStringHashData data; - NewedNode *newedNodes; - ReservedNodePool *nodePool; - const QStringHash<T> *link; - - template<typename K> - inline Node *findNode(const K &) const; - - inline Node *createNode(const Node &o); - - template<typename K> - inline Node *createNode(const K &, const T &); - - inline Node *insertNode(Node *, quint32); - - inline void initializeNode(Node *, const QHashedString &key); - inline void initializeNode(Node *, const QHashedCStringRef &key); - - template<typename K> - inline Node *takeNode(const K &key, const T &value); - - inline Node *takeNode(const Node &o); - - inline void copy(const QStringHash<T> &); - - void copyNode(const QStringHashNode *otherNode); - - inline QStringHashData::IteratorData iterateFirst() const; - static inline QStringHashData::IteratorData iterateNext(const QStringHashData::IteratorData &); - -public: - inline QStringHash(); - inline QStringHash(const QStringHash &); - inline ~QStringHash(); - - QStringHash &operator=(const QStringHash<T> &); - - void copyAndReserve(const QStringHash<T> &other, int additionalReserve); - void linkAndReserve(const QStringHash<T> &other, int additionalReserve); - - inline bool isEmpty() const; - inline void clear(); - inline int count() const; - - inline int numBuckets() const; - inline bool isLinked() const; - - class ConstIterator { - public: - inline ConstIterator(); - inline ConstIterator(const QStringHashData::IteratorData &); - - inline ConstIterator &operator++(); - - inline bool operator==(const ConstIterator &o) const; - inline bool operator!=(const ConstIterator &o) const; - - template<typename K> - inline bool equals(const K &) const; - - inline QHashedString key() const; - inline const T &value() const; - inline const T &operator*() const; - - inline Node *node() const; - private: - QStringHashData::IteratorData d; - }; - - template<typename K> - inline void insert(const K &, const T &); - - inline void insert(const ConstIterator &); - - template<typename K> - inline T *value(const K &) const; - - inline T *value(const QV4::String *string) const; - inline T *value(const ConstIterator &) const; - - template<typename K> - inline bool contains(const K &) const; - - template<typename K> - inline T &operator[](const K &); - - inline ConstIterator begin() const; - inline ConstIterator end() const; - - inline ConstIterator iterator(Node *n) const; - - template<typename K> - inline ConstIterator find(const K &) const; - - inline void reserve(int); -}; - -template<class T> -QStringHash<T>::QStringHash() -: newedNodes(nullptr), nodePool(nullptr), link(nullptr) -{ -} - -template<class T> -QStringHash<T>::QStringHash(const QStringHash<T> &other) -: newedNodes(nullptr), nodePool(nullptr), link(nullptr) -{ - data.numBits = other.data.numBits; - data.size = other.data.size; - reserve(other.count()); - copy(other); -} - -template<class T> -QStringHash<T> &QStringHash<T>::operator=(const QStringHash<T> &other) -{ - if (&other == this) - return *this; - - clear(); - - data.numBits = other.data.numBits; - data.size = other.data.size; - reserve(other.count()); - copy(other); - - return *this; -} - -template<class T> -void QStringHash<T>::copyAndReserve(const QStringHash<T> &other, int additionalReserve) -{ - clear(); - data.numBits = other.data.numBits; - reserve(other.count() + additionalReserve); - copy(other); -} - -template<class T> -void QStringHash<T>::linkAndReserve(const QStringHash<T> &other, int additionalReserve) -{ - clear(); - - if (other.count()) { - data.size = other.data.size; - data.rehashToSize(other.count() + additionalReserve); - - if (data.numBuckets == other.data.numBuckets) { - nodePool = new ReservedNodePool; - nodePool->count = additionalReserve; - nodePool->used = 0; - nodePool->nodes = new Node[additionalReserve]; - -#ifdef QSTRINGHASH_LINK_DEBUG - data.linkCount++; - const_cast<QStringHash<T>&>(other).data.linkCount++; -#endif - - for (int ii = 0; ii < data.numBuckets; ++ii) - data.buckets[ii] = (Node *)other.data.buckets[ii]; - - link = &other; - return; - } - - data.size = 0; - } - - data.numBits = other.data.numBits; - reserve(other.count() + additionalReserve); - copy(other); -} - -template<class T> -QStringHash<T>::~QStringHash() -{ - clear(); -} - -template<class T> -void QStringHash<T>::clear() -{ -#ifdef QSTRINGHASH_LINK_DEBUG - if (link) { - data.linkCount--; - const_cast<QStringHash<T> *>(link)->data.linkCount--; - } - - if (data.linkCount) - qFatal("QStringHash: Illegal attempt to clear a linked hash."); -#endif - - // Delete the individually allocated nodes - NewedNode *n = newedNodes; - while (n) { - NewedNode *c = n; - n = c->nextNewed; - delete c; - } - // Delete the pool allocated nodes - if (nodePool) delete nodePool; - delete [] data.buckets; - - data.buckets = nullptr; - data.numBuckets = 0; - data.numBits = 0; - data.size = 0; - - newedNodes = nullptr; - nodePool = nullptr; - link = nullptr; -} - -template<class T> -bool QStringHash<T>::isEmpty() const -{ - return data.size== 0; -} - -template<class T> -int QStringHash<T>::count() const -{ - return data.size; -} - -template<class T> -int QStringHash<T>::numBuckets() const -{ - return data.numBuckets; -} - -template<class T> -bool QStringHash<T>::isLinked() const -{ - return link != 0; -} - -template<class T> -void QStringHash<T>::initializeNode(Node *node, const QHashedString &key) -{ - node->length = key.length(); - node->hash = key.hash(); - node->strData = const_cast<QHashedString &>(key).data_ptr(); - node->strData->ref.ref(); - node->setQString(true); -} - -template<class T> -void QStringHash<T>::initializeNode(Node *node, const QHashedCStringRef &key) -{ - node->length = key.length(); - node->hash = key.hash(); - node->ckey = key.constData(); -} - -template<class T> -template<class K> -typename QStringHash<T>::Node *QStringHash<T>::takeNode(const K &key, const T &value) -{ - if (nodePool && nodePool->used != nodePool->count) { - Node *rv = nodePool->nodes + nodePool->used++; - initializeNode(rv, hashedString(key)); - rv->value = value; - return rv; - } else { - NewedNode *rv = new NewedNode(hashedString(key), value); - rv->nextNewed = newedNodes; - newedNodes = rv; - return rv; - } -} - -template<class T> -typename QStringHash<T>::Node *QStringHash<T>::takeNode(const Node &o) -{ - if (nodePool && nodePool->used != nodePool->count) { - Node *rv = nodePool->nodes + nodePool->used++; - rv->length = o.length; - rv->hash = o.hash; - if (o.isQString()) { - rv->strData = o.strData; - rv->strData->ref.ref(); - rv->setQString(true); - } else { - rv->ckey = o.ckey; - } - rv->symbolId = o.symbolId; - rv->value = o.value; - return rv; - } else { - NewedNode *rv = new NewedNode(o); - rv->nextNewed = newedNodes; - newedNodes = rv; - return rv; - } -} - -template<class T> -void QStringHash<T>::copyNode(const QStringHashNode *otherNode) -{ - // Copy the predecessor before the successor - QStringHashNode *next = otherNode->next.data(); - if (next) - copyNode(next); - - Node *mynode = takeNode(*(const Node *)otherNode); - int bucket = mynode->hash % data.numBuckets; - mynode->next = data.buckets[bucket]; - data.buckets[bucket] = mynode; -} - -template<class T> -void QStringHash<T>::copy(const QStringHash<T> &other) -{ - Q_ASSERT(data.size == 0); - - data.size = other.data.size; - - // Ensure buckets array is created - data.rehashToBits(data.numBits); - - // Preserve the existing order within buckets - for (int i = 0; i < other.data.numBuckets; ++i) { - QStringHashNode *bucket = other.data.buckets[i]; - if (bucket) - copyNode(bucket); - } -} - -template<class T> -QStringHashData::IteratorData -QStringHash<T>::iterateNext(const QStringHashData::IteratorData &d) -{ - QStringHash<T> *This = (QStringHash<T> *)d.p; - Node *node = (Node *)d.n; - - if (This->nodePool && node >= This->nodePool->nodes && - node < (This->nodePool->nodes + This->nodePool->used)) { - node--; - if (node < This->nodePool->nodes) - node = nullptr; - } else { - NewedNode *nn = (NewedNode *)node; - node = nn->nextNewed; - - if (node == nullptr && This->nodePool && This->nodePool->used) - node = This->nodePool->nodes + This->nodePool->used - 1; - } - - if (node == nullptr && This->link) - return This->link->iterateFirst(); - - QStringHashData::IteratorData rv; - rv.n = node; - rv.p = d.p; - return rv; -} - -template<class T> -QStringHashData::IteratorData QStringHash<T>::iterateFirst() const -{ - Node *n = nullptr; - if (newedNodes) - n = newedNodes; - else if (nodePool && nodePool->used) - n = nodePool->nodes + nodePool->used - 1; - - if (n == nullptr && link) - return link->iterateFirst(); - - QStringHashData::IteratorData rv; - rv.n = n; - rv.p = const_cast<QStringHash<T> *>(this); - return rv; -} - -template<class T> -typename QStringHash<T>::ConstIterator QStringHash<T>::iterator(Node *n) const -{ - if (!n) - return ConstIterator(); - - const QStringHash<T> *container = this; - - if (link) { - // This node could be in the linked hash - if ((n >= nodePool->nodes) && (n < (nodePool->nodes + nodePool->used))) { - // The node is in this hash - } else if ((n >= link->nodePool->nodes) && (n < (link->nodePool->nodes + link->nodePool->used))) { - // The node is in the linked hash - container = link; - } else { - const NewedNode *ln = link->newedNodes; - while (ln) { - if (ln == n) { - // This node is in the linked hash's newed list - container = link; - break; - } - ln = ln->nextNewed; - } - } - } - - QStringHashData::IteratorData rv; - rv.n = n; - rv.p = const_cast<QStringHash<T> *>(container); - return ConstIterator(rv); -} - -template<class T> -typename QStringHash<T>::Node *QStringHash<T>::createNode(const Node &o) -{ - Node *n = takeNode(o); - return insertNode(n, n->hash); -} - -template<class T> -template<class K> -typename QStringHash<T>::Node *QStringHash<T>::createNode(const K &key, const T &value) -{ - Node *n = takeNode(key, value); - return insertNode(n, hashOf(key)); -} - -template<class T> -typename QStringHash<T>::Node *QStringHash<T>::insertNode(Node *n, quint32 hash) -{ - if (data.size >= data.numBuckets) - data.rehashToBits(data.numBits + 1); - - int bucket = hash % data.numBuckets; - n->next = data.buckets[bucket]; - data.buckets[bucket] = n; - - data.size++; - - return n; -} - -template<class T> -template<class K> -void QStringHash<T>::insert(const K &key, const T &value) -{ - // If this is a linked hash, we can't rely on owning the node, so we always - // create a new one. - Node *n = link?nullptr:findNode(key); - if (n) n->value = value; - else createNode(key, value); -} - -template<class T> -void QStringHash<T>::insert(const ConstIterator &iter) -{ - insert(iter.key(), iter.value()); -} - -template<class T> -template<class K> -typename QStringHash<T>::Node *QStringHash<T>::findNode(const K &key) const -{ - QStringHashNode *node = data.numBuckets?data.buckets[hashOf(key) % data.numBuckets]:nullptr; - - typename HashedForm<K>::Type hashedKey(hashedString(key)); - while (node && !node->equals(hashedKey)) - node = (*node->next); - - return (Node *)node; -} - -template<class T> -template<class K> -T *QStringHash<T>::value(const K &key) const -{ - Node *n = findNode(key); - return n?&n->value:nullptr; -} - -template<class T> -T *QStringHash<T>::value(const ConstIterator &iter) const -{ - Node *n = iter.node(); - return value(n->key()); -} - -template<class T> -T *QStringHash<T>::value(const QV4::String *string) const -{ - Node *n = findNode(string); - return n?&n->value:nullptr; -} - -template<class T> -template<class K> -bool QStringHash<T>::contains(const K &key) const -{ - return nullptr != value(key); -} - -template<class T> -template<class K> -T &QStringHash<T>::operator[](const K &key) -{ - Node *n = findNode(key); - if (n) return n->value; - else return createNode(key, T())->value; -} - -template<class T> -void QStringHash<T>::reserve(int n) -{ - if (nodePool || 0 == n) - return; - - nodePool = new ReservedNodePool; - nodePool->count = n; - nodePool->used = 0; - nodePool->nodes = new Node[n]; - - data.rehashToSize(n); -} - -template<class T> -QStringHash<T>::ConstIterator::ConstIterator() -{ -} - -template<class T> -QStringHash<T>::ConstIterator::ConstIterator(const QStringHashData::IteratorData &d) -: d(d) -{ -} - -template<class T> -typename QStringHash<T>::ConstIterator &QStringHash<T>::ConstIterator::operator++() -{ - d = QStringHash<T>::iterateNext(d); - return *this; -} - -template<class T> -bool QStringHash<T>::ConstIterator::operator==(const ConstIterator &o) const -{ - return d.n == o.d.n; -} - -template<class T> -bool QStringHash<T>::ConstIterator::operator!=(const ConstIterator &o) const -{ - return d.n != o.d.n; -} - -template<class T> -template<typename K> -bool QStringHash<T>::ConstIterator::equals(const K &key) const -{ - return d.n->equals(key); -} - -template<class T> -QHashedString QStringHash<T>::ConstIterator::key() const -{ - Node *n = (Node *)d.n; - return n->key(); -} -template<class T> -const T &QStringHash<T>::ConstIterator::value() const -{ - Node *n = (Node *)d.n; - return n->value; -} - -template<class T> -const T &QStringHash<T>::ConstIterator::operator*() const -{ - Node *n = (Node *)d.n; - return n->value; -} - -template<class T> -typename QStringHash<T>::Node *QStringHash<T>::ConstIterator::node() const -{ - Node *n = (Node *)d.n; - return n; -} - -template<class T> -typename QStringHash<T>::ConstIterator QStringHash<T>::begin() const -{ - return ConstIterator(iterateFirst()); -} - -template<class T> -typename QStringHash<T>::ConstIterator QStringHash<T>::end() const -{ - return ConstIterator(); -} - -template<class T> -template<class K> -typename QStringHash<T>::ConstIterator QStringHash<T>::find(const K &key) const -{ - return iterator(findNode(key)); -} - -template<class T> -class QStringMultiHash : public QStringHash<T> -{ -public: - typedef typename QStringHash<T>::ConstIterator ConstIterator; - - template<typename K> - inline void insert(const K &, const T &); - - inline void insert(const ConstIterator &); - - inline ConstIterator findNext(const ConstIterator &) const; -}; - -template<class T> -template<class K> -void QStringMultiHash<T>::insert(const K &key, const T &value) -{ - // Always create a new node - QStringHash<T>::createNode(key, value); -} - -template<class T> -void QStringMultiHash<T>::insert(const ConstIterator &iter) -{ - // Always create a new node - QStringHash<T>::createNode(iter.key(), iter.value()); -} - -template<class T> -typename QStringHash<T>::ConstIterator QStringMultiHash<T>::findNext(const ConstIterator &iter) const -{ - QStringHashNode *node = iter.node(); - if (node) { - QHashedString key(node->key()); - - while ((node = *node->next)) { - if (node->equals(key)) { - return QStringHash<T>::iterator(static_cast<typename QStringHash<T>::Node *>(node)); - } - } - } - - return ConstIterator(); -} - inline uint qHash(const QHashedString &string) { return uint(string.hash()); diff --git a/src/qml/qml/ftw/qlinkedstringhash_p.h b/src/qml/qml/ftw/qlinkedstringhash_p.h new file mode 100644 index 0000000000..67ced7fbbf --- /dev/null +++ b/src/qml/qml/ftw/qlinkedstringhash_p.h @@ -0,0 +1,238 @@ +/**************************************************************************** +** +** 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 QLINKEDSTRINGHASH_P_H +#define QLINKEDSTRINGHASH_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 <private/qstringhash_p.h> + +QT_BEGIN_NAMESPACE + +template<class T> +class QLinkedStringHash : private QStringHash<T> +{ +public: + using typename QStringHash<T>::Node; + using typename QStringHash<T>::NewedNode; + using typename QStringHash<T>::ReservedNodePool; + using typename QStringHash<T>::mapped_type; + + using ConstIteratorData = QStringHashData::IteratorData<const QLinkedStringHash>; + using ConstIterator = typename QStringHash<T>::template Iterator<ConstIteratorData, const T>; + + void linkAndReserve(const QLinkedStringHash<T> &other, int additionalReserve) + { + clear(); + + if (other.count()) { + data.size = other.data.size; + data.rehashToSize(other.count() + additionalReserve); + + if (data.numBuckets == other.data.numBuckets) { + nodePool = new ReservedNodePool; + nodePool->count = additionalReserve; + nodePool->used = 0; + nodePool->nodes = new Node[additionalReserve]; + + for (int ii = 0; ii < data.numBuckets; ++ii) + data.buckets[ii] = (Node *)other.data.buckets[ii]; + + link = &other; + return; + } + + data.size = 0; + } + + data.numBits = other.data.numBits; + reserve(other.count() + additionalReserve); + copy(other); + } + + inline bool isLinked() const + { + return link != 0; + } + + void clear() + { + QStringHash<T>::clear(); + link = nullptr; + } + + template<typename K> + void insert(const K &key, const T &value) + { + // If this is a linked hash, we can't rely on owning the node, so we always + // create a new one. + Node *n = link ? nullptr : QStringHash<T>::findNode(key); + if (n) + n->value = value; + else + QStringHash<T>::createNode(key, value); + } + + template<typename K> + inline ConstIterator find(const K &key) const + { + return iterator(QStringHash<T>::findNode(key)); + } + + ConstIterator begin() const + { + return ConstIterator( + QStringHash<T>::template iterateFirst<const QLinkedStringHash<T>, + ConstIteratorData>(this)); + } + + ConstIterator end() const { return ConstIterator(); } + + inline T *value(const ConstIterator &iter) { return value(iter.node()->key()); } + + using QStringHash<T>::value; + using QStringHash<T>::reserve; + using QStringHash<T>::copy; + +protected: + friend QStringHash<T>; + using QStringHash<T>::data; + using QStringHash<T>::nodePool; + + using QStringHash<T>::createNode; + + inline ConstIteratorData iterateFirst() const + { + const ConstIteratorData rv + = QStringHash<T>::template iterateFirst<const QLinkedStringHash<T>, + ConstIteratorData>(this); + return (rv.n == nullptr && link) ? link->iterateFirst() : rv; + } + + static inline ConstIteratorData iterateNext(const ConstIteratorData &d) + { + const QLinkedStringHash<T> *self = d.p; + const ConstIteratorData rv = QStringHash<T>::iterateNext(d); + return (rv.n == nullptr && self->link) ? self->link->iterateFirst() : rv; + } + + inline ConstIterator iterator(Node *n) const + { + if (!n) + return ConstIterator(); + + const QLinkedStringHash<T> *container = this; + + if (link) { + // This node could be in the linked hash + if ((n >= nodePool->nodes) && (n < (nodePool->nodes + nodePool->used))) { + // The node is in this hash + } else if ((n >= link->nodePool->nodes) + && (n < (link->nodePool->nodes + link->nodePool->used))) { + // The node is in the linked hash + container = link; + } else { + const NewedNode *ln = link->newedNodes; + while (ln) { + if (ln == n) { + // This node is in the linked hash's newed list + container = link; + break; + } + ln = ln->nextNewed; + } + } + } + + + ConstIteratorData rv; + rv.n = n; + rv.p = container; + return ConstIterator(rv); + } + + const QLinkedStringHash<T> *link = nullptr; +}; + +template<class T> +class QLinkedStringMultiHash : public QLinkedStringHash<T> +{ +public: + using ConstIterator = typename QLinkedStringHash<T>::ConstIterator; + + template<typename K> + inline void insert(const K &key, const T &value) + { + // Always create a new node + QLinkedStringHash<T>::createNode(key, value); + } + + inline void insert(const ConstIterator &iter) + { + // Always create a new node + QLinkedStringHash<T>::createNode(iter.key(), iter.value()); + } + + inline ConstIterator findNext(const ConstIterator &iter) const + { + if (auto *node = iter.node()) { + QHashedString key(node->key()); + while ((node = static_cast<typename QLinkedStringHash<T>::Node *>(*node->next))) { + if (node->equals(key)) + return QLinkedStringHash<T>::iterator(node); + } + } + + return ConstIterator(); + } +}; + +QT_END_NAMESPACE + +#endif // QLINKEDSTRINGHASH_P_H diff --git a/src/qml/qml/ftw/qqmlrefcount_p.h b/src/qml/qml/ftw/qqmlrefcount_p.h index d32a08e0f5..140f129b21 100644 --- a/src/qml/qml/ftw/qqmlrefcount_p.h +++ b/src/qml/qml/ftw/qqmlrefcount_p.h @@ -60,18 +60,18 @@ QT_BEGIN_NAMESPACE class Q_QML_PRIVATE_EXPORT QQmlRefCount { + Q_DISABLE_COPY_MOVE(QQmlRefCount) public: inline QQmlRefCount(); - inline virtual ~QQmlRefCount(); - inline void addref(); - inline void release(); + inline void addref() const; + inline void release() const; inline int count() const; protected: - inline virtual void destroy(); + inline virtual ~QQmlRefCount(); private: - QAtomicInt refCount; + mutable QAtomicInt refCount; }; template<class T> @@ -116,17 +116,17 @@ QQmlRefCount::~QQmlRefCount() Q_ASSERT(refCount.load() == 0); } -void QQmlRefCount::addref() +void QQmlRefCount::addref() const { Q_ASSERT(refCount.load() > 0); refCount.ref(); } -void QQmlRefCount::release() +void QQmlRefCount::release() const { Q_ASSERT(refCount.load() > 0); if (!refCount.deref()) - destroy(); + delete this; } int QQmlRefCount::count() const @@ -134,11 +134,6 @@ int QQmlRefCount::count() const return refCount.load(); } -void QQmlRefCount::destroy() -{ - delete this; -} - template<class T> QQmlRefPointer<T>::QQmlRefPointer() : o(nullptr) diff --git a/src/qml/qml/ftw/qstringhash.cpp b/src/qml/qml/ftw/qstringhash.cpp new file mode 100644 index 0000000000..a483dcb810 --- /dev/null +++ b/src/qml/qml/ftw/qstringhash.cpp @@ -0,0 +1,168 @@ +/**************************************************************************** +** +** 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 "qstringhash_p.h" + +QT_BEGIN_NAMESPACE + +/* + A QHash has initially around pow(2, MinNumBits) buckets. For + example, if MinNumBits is 4, it has 17 buckets. +*/ +static const int MinNumBits = 4; + +/* + The prime_deltas array is a table of selected prime values, even + though it doesn't look like one. The primes we are using are 1, + 2, 5, 11, 17, 37, 67, 131, 257, ..., i.e. primes in the immediate + surrounding of a power of two. + + The primeForNumBits() function returns the prime associated to a + power of two. For example, primeForNumBits(8) returns 257. +*/ + +static const uchar prime_deltas[] = { + 0, 0, 1, 3, 1, 5, 3, 3, 1, 9, 7, 5, 3, 9, 25, 3, + 1, 21, 3, 21, 7, 15, 9, 5, 3, 29, 15, 0, 0, 0, 0, 0 +}; + +static inline int primeForNumBits(int numBits) +{ + return (1 << numBits) + prime_deltas[numBits]; +} + +void QStringHashData::rehashToSize(int size) +{ + short bits = qMax(MinNumBits, (int)numBits); + while (primeForNumBits(bits) < size) bits++; + + if (bits > numBits) + rehashToBits(bits); +} + +void QStringHashData::rehashToBits(short bits) +{ + numBits = qMax(MinNumBits, (int)bits); + + int nb = primeForNumBits(numBits); + if (nb == numBuckets && buckets) + return; + + QStringHashNode **newBuckets = new QStringHashNode *[nb]; + ::memset(newBuckets, 0, sizeof(QStringHashNode *) * nb); + + // Preserve the existing order within buckets so that items with the + // same key will retain the same find/findNext order + for (int i = 0; i < numBuckets; ++i) { + QStringHashNode *bucket = buckets[i]; + if (bucket) + rehashNode(newBuckets, nb, bucket); + } + + delete [] buckets; + buckets = newBuckets; + numBuckets = nb; +} + +void QStringHashData::rehashNode(QStringHashNode **newBuckets, int nb, QStringHashNode *node) +{ + QStringHashNode *next = node->next.data(); + if (next) + rehashNode(newBuckets, nb, next); + + int bucket = node->hash % nb; + node->next = newBuckets[bucket]; + newBuckets[bucket] = node; +} + +// Copy of QString's qMemCompare +bool QHashedString::compare(const QChar *lhs, const QChar *rhs, int length) +{ + Q_ASSERT(lhs && rhs); + const quint16 *a = (const quint16 *)lhs; + const quint16 *b = (const quint16 *)rhs; + + if (a == b || !length) + return true; + + union { + const quint16 *w; + const quint32 *d; + quintptr value; + } sa, sb; + sa.w = a; + sb.w = b; + + // check alignment + if ((sa.value & 2) == (sb.value & 2)) { + // both addresses have the same alignment + if (sa.value & 2) { + // both addresses are not aligned to 4-bytes boundaries + // compare the first character + if (*sa.w != *sb.w) + return false; + --length; + ++sa.w; + ++sb.w; + + // now both addresses are 4-bytes aligned + } + + // both addresses are 4-bytes aligned + // do a fast 32-bit comparison + const quint32 *e = sa.d + (length >> 1); + for ( ; sa.d != e; ++sa.d, ++sb.d) { + if (*sa.d != *sb.d) + return false; + } + + // do we have a tail? + return (length & 1) ? *sa.w == *sb.w : true; + } else { + // one of the addresses isn't 4-byte aligned but the other is + const quint16 *e = sa.w + length; + for ( ; sa.w != e; ++sa.w, ++sb.w) { + if (*sa.w != *sb.w) + return false; + } + } + return true; +} + +QT_END_NAMESPACE diff --git a/src/qml/qml/ftw/qstringhash_p.h b/src/qml/qml/ftw/qstringhash_p.h new file mode 100644 index 0000000000..c7251e8837 --- /dev/null +++ b/src/qml/qml/ftw/qstringhash_p.h @@ -0,0 +1,758 @@ +/**************************************************************************** +** +** 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 QSTRINGHASH_P_H +#define QSTRINGHASH_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 <private/qhashedstring_p.h> + +QT_BEGIN_NAMESPACE + +class QStringHashData; +class Q_AUTOTEST_EXPORT QStringHashNode +{ +public: + QStringHashNode() + : ckey(nullptr) + { + } + + QStringHashNode(const QHashedString &key) + : length(key.length()), hash(key.hash()), symbolId(0) + { + strData = const_cast<QHashedString &>(key).data_ptr(); + setQString(true); + strData->ref.ref(); + } + + QStringHashNode(const QHashedCStringRef &key) + : length(key.length()), hash(key.hash()), symbolId(0), ckey(key.constData()) + { + } + + QStringHashNode(const QStringHashNode &o) + : length(o.length), hash(o.hash), symbolId(o.symbolId), ckey(o.ckey) + { + setQString(o.isQString()); + if (isQString()) { strData->ref.ref(); } + } + + ~QStringHashNode() + { + if (isQString()) { if (!strData->ref.deref()) free(strData); } + } + + QFlagPointer<QStringHashNode> next; + + qint32 length = 0; + quint32 hash = 0; + quint32 symbolId = 0; + + union { + const char *ckey; + QStringData *strData; + }; + + inline QHashedString key() const + { + if (isQString()) + return QHashedString(QString((QChar *)strData->data(), length), hash); + + return QHashedString(QString::fromLatin1(ckey, length), hash); + } + + bool isQString() const { return next.flag(); } + void setQString(bool v) { if (v) next.setFlag(); else next.clearFlag(); } + + inline char *cStrData() const { return (char *)ckey; } + inline quint16 *utf16Data() const { return (quint16 *)strData->data(); } + + inline bool equals(const QV4::Value &string) const { + QString s = string.toQStringNoThrow(); + if (isQString()) { + QStringDataPtr dd; + dd.ptr = strData; + strData->ref.ref(); + return QString(dd) == s; + } else { + return QLatin1String(cStrData(), length) == s; + } + } + + inline bool equals(const QV4::String *string) const { + if (length != string->d()->length() || hash != string->hashValue()) + return false; + if (isQString()) { + QStringDataPtr dd; + dd.ptr = strData; + strData->ref.ref(); + return QString(dd) == string->toQString(); + } else { + return QLatin1String(cStrData(), length) == string->toQString(); + } + } + + inline bool equals(const QHashedStringRef &string) const { + return length == string.length() && + hash == string.hash() && + (isQString()?QHashedString::compare(string.constData(), (const QChar *)utf16Data(), length): + QHashedString::compare(string.constData(), cStrData(), length)); + } + + inline bool equals(const QHashedCStringRef &string) const { + return length == string.length() && + hash == string.hash() && + (isQString()?QHashedString::compare((const QChar *)utf16Data(), string.constData(), length): + QHashedString::compare(string.constData(), cStrData(), length)); + } +}; + +class Q_AUTOTEST_EXPORT QStringHashData +{ +public: + QStringHashData() {} + + QStringHashNode **buckets = nullptr; + int numBuckets = 0; + int size = 0; + short numBits = 0; + + template<typename StringHash> + struct IteratorData { + IteratorData(QStringHashNode *n = nullptr, StringHash *p = nullptr) : n(n), p(p) {} + + template<typename OtherData> + IteratorData(const OtherData &other) : n(other.n), p(other.p) {} + + QStringHashNode *n; + StringHash *p; + }; + void rehashToBits(short); + void rehashToSize(int); + void rehashNode(QStringHashNode **newBuckets, int nb, QStringHashNode *node); + +private: + QStringHashData(const QStringHashData &); + QStringHashData &operator=(const QStringHashData &); +}; + +// For a supplied key type, in what form do we need to keep a hashed version? +template<typename T> +struct HashedForm {}; + +template<> struct HashedForm<QString> { typedef QHashedString Type; }; +template<> struct HashedForm<QStringRef> { typedef QHashedStringRef Type; }; +template<> struct HashedForm<QHashedString> { typedef const QHashedString &Type; }; +template<> struct HashedForm<QV4::String *> { typedef const QV4::String *Type; }; +template<> struct HashedForm<const QV4::String *> { typedef const QV4::String *Type; }; +template<> struct HashedForm<QHashedStringRef> { typedef const QHashedStringRef &Type; }; +template<> struct HashedForm<QLatin1String> { typedef QHashedCStringRef Type; }; +template<> struct HashedForm<QHashedCStringRef> { typedef const QHashedCStringRef &Type; }; + +class QStringHashBase +{ +public: + static HashedForm<QString>::Type hashedString(const QString &s) { return QHashedString(s);} + static HashedForm<QStringRef>::Type hashedString(const QStringRef &s) { return QHashedStringRef(s.constData(), s.size());} + static HashedForm<QHashedString>::Type hashedString(const QHashedString &s) { return s; } + static HashedForm<QV4::String *>::Type hashedString(QV4::String *s) { return s; } + static HashedForm<const QV4::String *>::Type hashedString(const QV4::String *s) { return s; } + static HashedForm<QHashedStringRef>::Type hashedString(const QHashedStringRef &s) { return s; } + + static HashedForm<QLatin1String>::Type hashedString(const QLatin1String &s) { return QHashedCStringRef(s.data(), s.size()); } + static HashedForm<QHashedCStringRef>::Type hashedString(const QHashedCStringRef &s) { return s; } + + static const QString &toQString(const QString &s) { return s; } + static const QString &toQString(const QHashedString &s) { return s; } + static QString toQString(const QV4::String *s) { return s->toQString(); } + static QString toQString(const QHashedStringRef &s) { return s.toString(); } + + static QString toQString(const QLatin1String &s) { return QString(s); } + static QString toQString(const QHashedCStringRef &s) { return s.toUtf16(); } + + static inline quint32 hashOf(const QHashedStringRef &s) { return s.hash(); } + static inline quint32 hashOf(QV4::String *s) { return s->hashValue(); } + static inline quint32 hashOf(const QV4::String *s) { return s->hashValue(); } + + template<typename K> + static inline quint32 hashOf(const K &key) { return hashedString(key).hash(); } +}; + +template<class T> +class QStringHash : public QStringHashBase +{ +public: + typedef QHashedString key_type; + typedef T mapped_type; + + using MutableIteratorData = QStringHashData::IteratorData<QStringHash<T>>; + using ConstIteratorData = QStringHashData::IteratorData<const QStringHash<T>>; + + struct Node : public QStringHashNode { + Node(const QHashedString &key, const T &value) : QStringHashNode(key), value(value) {} + Node(const QHashedCStringRef &key, const T &value) : QStringHashNode(key), value(value) {} + Node(const Node &o) : QStringHashNode(o), value(o.value) {} + Node() {} + T value; + }; + struct NewedNode : public Node { + NewedNode(const QHashedString &key, const T &value) : Node(key, value), nextNewed(nullptr) {} + NewedNode(const QHashedCStringRef &key, const T &value) : Node(key, value), nextNewed(nullptr) {} + NewedNode(const Node &o) : Node(o), nextNewed(nullptr) {} + NewedNode *nextNewed; + }; + struct ReservedNodePool + { + ReservedNodePool() : nodes(nullptr) {} + ~ReservedNodePool() { delete [] nodes; } + int count = 0; + int used = 0; + Node *nodes; + }; + + QStringHashData data; + NewedNode *newedNodes; + ReservedNodePool *nodePool; + + template<typename K> + inline Node *findNode(const K &) const; + + inline Node *createNode(const Node &o); + + template<typename K> + inline Node *createNode(const K &, const T &); + + inline Node *insertNode(Node *, quint32); + + inline void initializeNode(Node *, const QHashedString &key); + inline void initializeNode(Node *, const QHashedCStringRef &key); + + template<typename K> + inline Node *takeNode(const K &key, const T &value); + + inline Node *takeNode(const Node &o); + + inline void copy(const QStringHash<T> &); + + void copyNode(const QStringHashNode *otherNode); + + template<typename StringHash, typename Data> + static inline Data iterateFirst(StringHash *self); + + template<typename Data> + static inline Data iterateNext(const Data &); + +public: + inline QStringHash(); + inline QStringHash(const QStringHash &); + inline ~QStringHash(); + + QStringHash &operator=(const QStringHash<T> &); + + void copyAndReserve(const QStringHash<T> &other, int additionalReserve); + + inline bool isEmpty() const; + inline void clear(); + inline int count() const; + + inline int numBuckets() const; + + template<typename Data, typename Value> + class Iterator { + public: + inline Iterator() = default; + inline Iterator(const Data &d) : d(d) {} + + inline Iterator &operator++() + { + d = QStringHash<T>::iterateNext(d); + return *this; + } + + inline bool operator==(const Iterator &o) const { return d.n == o.d.n; } + inline bool operator!=(const Iterator &o) const { return d.n != o.d.n; } + + template<typename K> + inline bool equals(const K &key) const { return d.n->equals(key); } + + inline QHashedString key() const { return static_cast<Node *>(d.n)->key(); } + inline Value &value() const { return static_cast<Node *>(d.n)->value; } + inline Value &operator*() const { return static_cast<Node *>(d.n)->value; } + + Node *node() const { return static_cast<Node *>(d.n); } + private: + Data d; + }; + + using MutableIterator = Iterator<MutableIteratorData, T>; + using ConstIterator = Iterator<ConstIteratorData, const T>; + + template<typename K> + inline void insert(const K &, const T &); + inline void insert(const MutableIterator &); + inline void insert(const ConstIterator &); + + template<typename K> + inline T *value(const K &) const; + inline T *value(const QV4::String *string) const; + inline T *value(const MutableIterator &) const; + inline T *value(const ConstIterator &) const; + + template<typename K> + inline bool contains(const K &) const; + + template<typename K> + inline T &operator[](const K &); + + inline MutableIterator begin(); + inline ConstIterator begin() const; + inline ConstIterator constBegin() const { return begin(); } + + inline MutableIterator end(); + inline ConstIterator end() const; + inline ConstIterator constEnd() const { return end(); } + + template<typename K> + inline MutableIterator find(const K &); + + template<typename K> + inline ConstIterator find(const K &) const; + + inline void reserve(int); +}; + +template<class T> +QStringHash<T>::QStringHash() +: newedNodes(nullptr), nodePool(nullptr) +{ +} + +template<class T> +QStringHash<T>::QStringHash(const QStringHash<T> &other) +: newedNodes(nullptr), nodePool(nullptr) +{ + data.numBits = other.data.numBits; + data.size = other.data.size; + reserve(other.count()); + copy(other); +} + +template<class T> +QStringHash<T> &QStringHash<T>::operator=(const QStringHash<T> &other) +{ + if (&other == this) + return *this; + + clear(); + + data.numBits = other.data.numBits; + data.size = other.data.size; + reserve(other.count()); + copy(other); + + return *this; +} + +template<class T> +void QStringHash<T>::copyAndReserve(const QStringHash<T> &other, int additionalReserve) +{ + clear(); + data.numBits = other.data.numBits; + reserve(other.count() + additionalReserve); + copy(other); +} + +template<class T> +QStringHash<T>::~QStringHash() +{ + clear(); +} + +template<class T> +void QStringHash<T>::clear() +{ + // Delete the individually allocated nodes + NewedNode *n = newedNodes; + while (n) { + NewedNode *c = n; + n = c->nextNewed; + delete c; + } + // Delete the pool allocated nodes + if (nodePool) delete nodePool; + delete [] data.buckets; + + data.buckets = nullptr; + data.numBuckets = 0; + data.numBits = 0; + data.size = 0; + + newedNodes = nullptr; + nodePool = nullptr; +} + +template<class T> +bool QStringHash<T>::isEmpty() const +{ + return data.size== 0; +} + +template<class T> +int QStringHash<T>::count() const +{ + return data.size; +} + +template<class T> +int QStringHash<T>::numBuckets() const +{ + return data.numBuckets; +} + +template<class T> +void QStringHash<T>::initializeNode(Node *node, const QHashedString &key) +{ + node->length = key.length(); + node->hash = key.hash(); + node->strData = const_cast<QHashedString &>(key).data_ptr(); + node->strData->ref.ref(); + node->setQString(true); +} + +template<class T> +void QStringHash<T>::initializeNode(Node *node, const QHashedCStringRef &key) +{ + node->length = key.length(); + node->hash = key.hash(); + node->ckey = key.constData(); +} + +template<class T> +template<class K> +typename QStringHash<T>::Node *QStringHash<T>::takeNode(const K &key, const T &value) +{ + if (nodePool && nodePool->used != nodePool->count) { + Node *rv = nodePool->nodes + nodePool->used++; + initializeNode(rv, hashedString(key)); + rv->value = value; + return rv; + } else { + NewedNode *rv = new NewedNode(hashedString(key), value); + rv->nextNewed = newedNodes; + newedNodes = rv; + return rv; + } +} + +template<class T> +typename QStringHash<T>::Node *QStringHash<T>::takeNode(const Node &o) +{ + if (nodePool && nodePool->used != nodePool->count) { + Node *rv = nodePool->nodes + nodePool->used++; + rv->length = o.length; + rv->hash = o.hash; + if (o.isQString()) { + rv->strData = o.strData; + rv->strData->ref.ref(); + rv->setQString(true); + } else { + rv->ckey = o.ckey; + } + rv->symbolId = o.symbolId; + rv->value = o.value; + return rv; + } else { + NewedNode *rv = new NewedNode(o); + rv->nextNewed = newedNodes; + newedNodes = rv; + return rv; + } +} + +template<class T> +void QStringHash<T>::copyNode(const QStringHashNode *otherNode) +{ + // Copy the predecessor before the successor + QStringHashNode *next = otherNode->next.data(); + if (next) + copyNode(next); + + Node *mynode = takeNode(*(const Node *)otherNode); + int bucket = mynode->hash % data.numBuckets; + mynode->next = data.buckets[bucket]; + data.buckets[bucket] = mynode; +} + +template<class T> +void QStringHash<T>::copy(const QStringHash<T> &other) +{ + Q_ASSERT(data.size == 0); + + data.size = other.data.size; + + // Ensure buckets array is created + data.rehashToBits(data.numBits); + + // Preserve the existing order within buckets + for (int i = 0; i < other.data.numBuckets; ++i) { + QStringHashNode *bucket = other.data.buckets[i]; + if (bucket) + copyNode(bucket); + } +} + +template<class T> +template<typename Data> +Data QStringHash<T>::iterateNext(const Data &d) +{ + auto *This = d.p; + Node *node = (Node *)d.n; + + if (This->nodePool && node >= This->nodePool->nodes && + node < (This->nodePool->nodes + This->nodePool->used)) { + node--; + if (node < This->nodePool->nodes) + node = nullptr; + } else { + NewedNode *nn = (NewedNode *)node; + node = nn->nextNewed; + + if (node == nullptr && This->nodePool && This->nodePool->used) + node = This->nodePool->nodes + This->nodePool->used - 1; + } + + Data rv; + rv.n = node; + rv.p = d.p; + return rv; +} + +template<class T> +template<typename StringHash, typename Data> +Data QStringHash<T>::iterateFirst(StringHash *self) +{ + typename StringHash::Node *n = nullptr; + if (self->newedNodes) + n = self->newedNodes; + else if (self->nodePool && self->nodePool->used) + n = self->nodePool->nodes + self->nodePool->used - 1; + + Data rv; + rv.n = n; + rv.p = self; + return rv; +} + +template<class T> +typename QStringHash<T>::Node *QStringHash<T>::createNode(const Node &o) +{ + Node *n = takeNode(o); + return insertNode(n, n->hash); +} + +template<class T> +template<class K> +typename QStringHash<T>::Node *QStringHash<T>::createNode(const K &key, const T &value) +{ + Node *n = takeNode(key, value); + return insertNode(n, hashOf(key)); +} + +template<class T> +typename QStringHash<T>::Node *QStringHash<T>::insertNode(Node *n, quint32 hash) +{ + if (data.size >= data.numBuckets) + data.rehashToBits(data.numBits + 1); + + int bucket = hash % data.numBuckets; + n->next = data.buckets[bucket]; + data.buckets[bucket] = n; + + data.size++; + + return n; +} + +template<class T> +template<class K> +void QStringHash<T>::insert(const K &key, const T &value) +{ + Node *n = findNode(key); + if (n) + n->value = value; + else + createNode(key, value); +} + +template<class T> +void QStringHash<T>::insert(const MutableIterator &iter) +{ + insert(iter.key(), iter.value()); +} + +template<class T> +void QStringHash<T>::insert(const ConstIterator &iter) +{ + insert(iter.key(), iter.value()); +} + +template<class T> +template<class K> +typename QStringHash<T>::Node *QStringHash<T>::findNode(const K &key) const +{ + QStringHashNode *node = data.numBuckets?data.buckets[hashOf(key) % data.numBuckets]:nullptr; + + typename HashedForm<K>::Type hashedKey(hashedString(key)); + while (node && !node->equals(hashedKey)) + node = (*node->next); + + return (Node *)node; +} + +template<class T> +template<class K> +T *QStringHash<T>::value(const K &key) const +{ + Node *n = findNode(key); + return n?&n->value:nullptr; +} + +template<typename T> +T *QStringHash<T>::value(const MutableIterator &iter) const +{ + return value(iter.node()->key()); +} + +template<class T> +T *QStringHash<T>::value(const ConstIterator &iter) const +{ + return value(iter.node()->key()); +} + +template<class T> +T *QStringHash<T>::value(const QV4::String *string) const +{ + Node *n = findNode(string); + return n?&n->value:nullptr; +} + +template<class T> +template<class K> +bool QStringHash<T>::contains(const K &key) const +{ + return nullptr != value(key); +} + +template<class T> +template<class K> +T &QStringHash<T>::operator[](const K &key) +{ + Node *n = findNode(key); + if (n) return n->value; + else return createNode(key, T())->value; +} + +template<class T> +void QStringHash<T>::reserve(int n) +{ + if (nodePool || 0 == n) + return; + + nodePool = new ReservedNodePool; + nodePool->count = n; + nodePool->used = 0; + nodePool->nodes = new Node[n]; + + data.rehashToSize(n); +} + +template<class T> +typename QStringHash<T>::MutableIterator QStringHash<T>::begin() +{ + return MutableIterator(iterateFirst<QStringHash<T>, MutableIteratorData>(this)); +} + +template<class T> +typename QStringHash<T>::ConstIterator QStringHash<T>::begin() const +{ + return ConstIterator(iterateFirst<const QStringHash<T>, ConstIteratorData>(this)); +} + +template<class T> +typename QStringHash<T>::MutableIterator QStringHash<T>::end() +{ + return MutableIterator(); +} + +template<class T> +typename QStringHash<T>::ConstIterator QStringHash<T>::end() const +{ + return ConstIterator(); +} + +template<class T> +template<class K> +typename QStringHash<T>::MutableIterator QStringHash<T>::find(const K &key) +{ + Node *n = findNode(key); + return n ? MutableIterator(MutableIteratorData(n, this)) : MutableIterator(); +} + +template<class T> +template<class K> +typename QStringHash<T>::ConstIterator QStringHash<T>::find(const K &key) const +{ + Node *n = findNode(key); + return n ? ConstIterator(ConstIteratorData(n, this)) : ConstIterator(); +} + +QT_END_NAMESPACE + +#endif // QSTRINGHASH_P_H diff --git a/src/qml/qml/qml.pri b/src/qml/qml/qml.pri index ca13ce9211..0895e5ae68 100644 --- a/src/qml/qml/qml.pri +++ b/src/qml/qml/qml.pri @@ -1,4 +1,5 @@ SOURCES += \ + $$PWD/qqml.cpp \ $$PWD/qqmlopenmetaobject.cpp \ $$PWD/qqmlvmemetaobject.cpp \ $$PWD/qqmlengine.cpp \ @@ -14,14 +15,21 @@ SOURCES += \ $$PWD/qqmlvme.cpp \ $$PWD/qqmlboundsignal.cpp \ $$PWD/qqmlmetatype.cpp \ + $$PWD/qqmlmetatypedata.cpp \ $$PWD/qqmlstringconverters.cpp \ + $$PWD/qqmltype.cpp \ + $$PWD/qqmltypemodule.cpp \ + $$PWD/qqmltypemoduleversion.cpp \ $$PWD/qqmlparserstatus.cpp \ $$PWD/qqmltypeloader.cpp \ $$PWD/qqmlinfo.cpp \ $$PWD/qqmlvaluetype.cpp \ $$PWD/qqmlcleanup.cpp \ $$PWD/qqmlpropertycache.cpp \ + $$PWD/qqmlmetaobject.cpp \ $$PWD/qqmlnotifier.cpp \ + $$PWD/qqmlobjectorgadget.cpp \ + $$PWD/qqmlstaticmetaobject.cpp \ $$PWD/qqmltypenotavailable.cpp \ $$PWD/qqmltypenamecache.cpp \ $$PWD/qqmlscriptstring.cpp \ @@ -68,6 +76,12 @@ HEADERS += \ $$PWD/qqmlexpression_p.h \ $$PWD/qqmlprivate.h \ $$PWD/qqmlmetatype_p.h \ + $$PWD/qqmlmetatypedata_p.h \ + $$PWD/qqmltype_p.h \ + $$PWD/qqmltype_p_p.h \ + $$PWD/qqmltypemodule_p.h \ + $$PWD/qqmltypemodule_p_p.h \ + $$PWD/qqmltypemoduleversion_p.h \ $$PWD/qqmlengine.h \ $$PWD/qqmlcontext.h \ $$PWD/qqmlexpression.h \ @@ -81,9 +95,18 @@ HEADERS += \ $$PWD/qqmldata_p.h \ $$PWD/qqmlvaluetype_p.h \ $$PWD/qqmlcleanup_p.h \ + $$PWD/qqmlenumdata_p.h \ + $$PWD/qqmlenumvalue_p.h \ $$PWD/qqmlpropertycache_p.h \ + $$PWD/qqmlpropertycachemethodarguments_p.h \ + $$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 \ + $$PWD/qqmlstaticmetaobject_p.h \ $$PWD/qqmltypenotavailable_p.h \ $$PWD/qqmltypenamecache_p.h \ $$PWD/qqmlscriptstring.h \ diff --git a/src/qml/qml/qqml.cpp b/src/qml/qml/qqml.cpp new file mode 100644 index 0000000000..c1a8ed2a3d --- /dev/null +++ b/src/qml/qml/qqml.cpp @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** 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 "qqml.h" + +#include <QtQml/qqmlprivate.h> + +#include <private/qqmlengine_p.h> +#include <private/qqmlmetatype_p.h> +#include <private/qqmlmetatypedata_p.h> +#include <private/qqmltype_p_p.h> +#include <private/qqmltypemodule_p_p.h> + +#include <QtCore/qmutex.h> + +QT_BEGIN_NAMESPACE + +void qmlClearTypeRegistrations() // Declared in qqml.h +{ + QQmlMetaType::clearTypeRegistrations(); + QQmlEnginePrivate::baseModulesUninitialized = true; //So the engine re-registers its types +#if QT_CONFIG(library) + qmlClearEnginePlugins(); +#endif +} + +//From qqml.h +bool qmlProtectModule(const char *uri, int majVersion) +{ + return QQmlMetaType::protectModule(uri, majVersion); +} + +//From qqml.h +void qmlRegisterModule(const char *uri, int versionMajor, int versionMinor) +{ + QQmlMetaType::registerModule(uri, versionMajor, versionMinor); +} + +//From qqml.h +int qmlTypeId(const char *uri, int versionMajor, int versionMinor, const char *qmlName) +{ + return QQmlMetaType::typeId(uri, versionMajor, versionMinor, qmlName); +} + +/* +This method is "over generalized" to allow us to (potentially) register more types of things in +the future without adding exported symbols. +*/ +int QQmlPrivate::qmlregister(RegistrationType type, void *data) +{ + if (type == AutoParentRegistration) { + return QQmlMetaType::registerAutoParentFunction( + *reinterpret_cast<RegisterAutoParent *>(data)); + } else if (type == QmlUnitCacheHookRegistration) { + return QQmlMetaType::registerUnitCacheHook( + *reinterpret_cast<RegisterQmlUnitCacheHook *>(data)); + } + + QQmlType dtype; + if (type == TypeRegistration) + dtype = QQmlMetaType::registerType(*reinterpret_cast<RegisterType *>(data)); + else if (type == InterfaceRegistration) + dtype = QQmlMetaType::registerInterface(*reinterpret_cast<RegisterInterface *>(data)); + else if (type == SingletonRegistration) + dtype = QQmlMetaType::registerSingletonType(*reinterpret_cast<RegisterSingletonType *>(data)); + else if (type == CompositeRegistration) + dtype = QQmlMetaType::registerCompositeType(*reinterpret_cast<RegisterCompositeType *>(data)); + else if (type == CompositeSingletonRegistration) + dtype = QQmlMetaType::registerCompositeSingletonType(*reinterpret_cast<RegisterCompositeSingletonType *>(data)); + else + return -1; + + if (!dtype.isValid()) + return -1; + + QQmlMetaType::registerUndeletableType(dtype); + return dtype.index(); +} + +QT_END_NAMESPACE diff --git a/src/qml/qml/qqmlboundsignal.cpp b/src/qml/qml/qqmlboundsignal.cpp index e5b78591e0..cf6f831818 100644 --- a/src/qml/qml/qqmlboundsignal.cpp +++ b/src/qml/qml/qqmlboundsignal.cpp @@ -44,7 +44,6 @@ #include "qqmlengine_p.h" #include "qqmlexpression_p.h" #include "qqmlcontext_p.h" -#include "qqmlmetatype_p.h" #include "qqml.h" #include "qqmlcontext.h" #include "qqmlglobal_p.h" diff --git a/src/qml/qml/qqmlcustomparser_p.h b/src/qml/qml/qqmlcustomparser_p.h index bf28bca447..aa933553a8 100644 --- a/src/qml/qml/qqmlcustomparser_p.h +++ b/src/qml/qml/qqmlcustomparser_p.h @@ -51,7 +51,6 @@ // We mean it. // -#include "qqmlmetatype_p.h" #include "qqmlerror.h" #include "qqmlbinding_p.h" #include <private/qqmltypecompiler_p.h> 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 <private/qv8engine_p.h> #include <private/qqmlengine_p.h> #include <private/qqmljavascriptexpression_p.h> #include <private/qv4value_p.h> diff --git a/src/qml/qml/qqmlengine_p.h b/src/qml/qml/qqmlengine_p.h index d05c945ae4..20d451d607 100644 --- a/src/qml/qml/qqmlengine_p.h +++ b/src/qml/qml/qqmlengine_p.h @@ -62,7 +62,6 @@ #include "qqmlcontext_p.h" #include "qqmlexpression.h" #include "qqmlproperty_p.h" -#include "qqmlpropertycache_p.h" #include "qqmlmetatype_p.h" #include <private/qintrusivelist_p.h> #include <private/qrecyclepool_p.h> @@ -101,6 +100,7 @@ class QDir; class QQmlIncubator; class QQmlProfiler; class QQmlPropertyCapture; +class QQmlMetaObject; // This needs to be declared here so that the pool for it can live in QQmlEnginePrivate. // The inline method definitions are in qqmljavascriptexpression_p.h diff --git a/src/qml/qml/qqmlenumdata_p.h b/src/qml/qml/qqmlenumdata_p.h new file mode 100644 index 0000000000..df99c2c1bc --- /dev/null +++ b/src/qml/qml/qqmlenumdata_p.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** 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 QQMLENUMDATA_P_H +#define QQMLENUMDATA_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 <private/qqmlenumvalue_p.h> + +QT_BEGIN_NAMESPACE + +struct QQmlEnumData +{ + QString name; + QVector<QQmlEnumValue> values; +}; + +QT_END_NAMESPACE + +#endif // QQMLENUMDATA_P_H diff --git a/src/qml/qml/qqmlenumvalue_p.h b/src/qml/qml/qqmlenumvalue_p.h new file mode 100644 index 0000000000..ea0fc244cb --- /dev/null +++ b/src/qml/qml/qqmlenumvalue_p.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** 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 QQMLENUMVALUE_P_H +#define QQMLENUMVALUE_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 <QtCore/qstring.h> + +QT_BEGIN_NAMESPACE + +struct QQmlEnumValue +{ + QQmlEnumValue() {} + QQmlEnumValue(const QString &n, int v) : namedValue(n), value(v) {} + QString namedValue; + int value = -1; +}; + +QT_END_NAMESPACE + +#endif // QQMLENUMVALUE_P_H 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 <private/qv8engine_p.h> #include <private/qqmlsourcecoordinate_p.h> #include <QtCore/qdebug.h> 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() {} diff --git a/src/qml/qml/qqmlglobal_p.h b/src/qml/qml/qqmlglobal_p.h index 818537560c..e2d53ab555 100644 --- a/src/qml/qml/qqmlglobal_p.h +++ b/src/qml/qml/qqmlglobal_p.h @@ -53,7 +53,7 @@ #include <private/qtqmlglobal_p.h> #include <QtCore/QObject> -#include <private/qqmlpropertycache_p.h> +#include <private/qqmlmetaobject_p.h> #include <private/qmetaobject_p.h> #include <private/qv8engine_p.h> diff --git a/src/qml/qml/qqmlimport.cpp b/src/qml/qml/qqmlimport.cpp index a79d4d074c..d2d37164a7 100644 --- a/src/qml/qml/qqmlimport.cpp +++ b/src/qml/qml/qqmlimport.cpp @@ -54,6 +54,7 @@ #include <private/qqmltypenamecache_p.h> #include <private/qqmlengine_p.h> #include <private/qfieldlist_p.h> +#include <private/qqmltypemodule_p.h> #include <QtCore/qjsonobject.h> #include <QtCore/qjsonarray.h> @@ -131,85 +132,6 @@ bool isPathAbsolute(const QString &path) #endif } -/* - \internal - - Fetches the QQmlType instance registered for \a urlString, creating a - registration for it if it is not already registered, using the associated - \a typeName, \a isCompositeSingleton, \a majorVersion and \a minorVersion - details. - - Errors (if there are any) are placed into \a errors, if it is nonzero. Note - that errors are treated as fatal if \a errors is not set. -*/ -QQmlType fetchOrCreateTypeForUrl(const QString &urlString, const QHashedStringRef& typeName, - bool isCompositeSingleton, QList<QQmlError> *errors, - int majorVersion=-1, int minorVersion=-1) -{ - QUrl url(urlString); // ### unfortunate (costly) conversion - QQmlType ret = QQmlMetaType::qmlType(url); - if (ret.isValid()) - return ret; - - int dot = typeName.indexOf(QLatin1Char('.')); - QHashedStringRef unqualifiedtype = dot < 0 ? typeName : QHashedStringRef(typeName.constData() + dot + 1, typeName.length() - dot - 1); - - // We need a pointer, but we were passed a string. Take a copy so we - // can guarentee it will live long enough to reach qmlregister. - QByteArray buf(unqualifiedtype.toString().toUtf8()); - - QQmlMetaTypeRegistrationFailureRecorder failureRecorder; - - // Register the type. Note that the URI parameters here are empty; for - // file type imports, we do not place them in a URI as we don't - // necessarily have a good and unique one (picture a library import, - // which may be found in multiple plugin locations on disk), but there - // are other reasons for this too. - // - // By not putting them in a URI, we prevent the types from being - // registered on a QQmlTypeModule; this is important, as once types are - // placed on there, they cannot be easily removed, meaning if the - // developer subsequently loads a different import (meaning different - // types) with the same URI (using, say, a different plugin path), it is - // very undesirable that we continue to associate the types from the - // "old" URI with that new module. - // - // Not having URIs also means that the types cannot be found by name - // etc, the only way to look them up is through QQmlImports -- for - // better or worse. - if (isCompositeSingleton) { - QQmlPrivate::RegisterCompositeSingletonType reg = { - url, - "", // uri - majorVersion, - minorVersion, - buf.constData() - }; - ret = QQmlMetaType::registerCompositeSingletonType(reg); - } else { - QQmlPrivate::RegisterCompositeType reg = { - url, - "", // uri - majorVersion, - minorVersion, - buf.constData() - }; - ret = QQmlMetaType::registerCompositeType(reg); - } - - // This means that the type couldn't be found by URL, but could not be - // registered either, meaning we most likely were passed some kind of bad - // data. - if (!ret.isValid()) { - if (!errors) // Cannot list errors properly, just quit - qFatal("%s", failureRecorder.failures().join('\n').toLatin1().constData()); - QQmlError error; - error.setDescription(failureRecorder.failures().join('\n')); - errors->prepend(error); - } - return ret; -} - } // namespace struct RegisteredPlugin { @@ -219,6 +141,13 @@ struct RegisteredPlugin { struct StringRegisteredPluginMap : public QMap<QString, RegisteredPlugin> { 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 @@ -820,9 +749,9 @@ bool QQmlImportInstance::resolveType(QQmlTypeLoader *typeLoader, const QHashedSt if (candidate != end) { if (!base) // ensure we have a componentUrl componentUrl = resolveLocalUrl(QString(url + candidate->typeName + dotqml_string), candidate->fileName); - QQmlType returnType = fetchOrCreateTypeForUrl(componentUrl, type, isCompositeSingleton, - nullptr, candidate->majorVersion, - candidate->minorVersion); + QQmlType returnType = QQmlMetaType::typeForUrl(componentUrl, type, isCompositeSingleton, + nullptr, candidate->majorVersion, + candidate->minorVersion); if (vmajor) *vmajor = candidate->majorVersion; if (vminor) @@ -866,8 +795,8 @@ bool QQmlImportInstance::resolveType(QQmlTypeLoader *typeLoader, const QHashedSt if (typeRecursionDetected) *typeRecursionDetected = true; } else { - QQmlType returnType = fetchOrCreateTypeForUrl( - qmlUrl, type, registrationType == QQmlType::CompositeSingletonType, nullptr); + QQmlType returnType = QQmlMetaType::typeForUrl( + qmlUrl, type, registrationType == QQmlType::CompositeSingletonType, nullptr); if (type_return) *type_return = returnType; return returnType.isValid(); @@ -915,7 +844,10 @@ bool QQmlImportsPrivate::resolveType(const QHashedStringRef& type, int *vmajor, return true; if (s->imports.count() == 1 && !s->imports.at(0)->isLibrary && type_return && s != &unqualifiedset) { // qualified, and only 1 url - *type_return = fetchOrCreateTypeForUrl(resolveLocalUrl(s->imports.at(0)->url, unqualifiedtype.toString() + QLatin1String(".qml")), type, false, errors); + *type_return = QQmlMetaType::typeForUrl( + resolveLocalUrl(s->imports.at(0)->url, + unqualifiedtype.toString() + QLatin1String(".qml")), + type, false, errors); return type_return->isValid(); } } @@ -2033,77 +1965,7 @@ bool QQmlImportDatabase::registerPluginTypes(QObject *instance, const QString &b { if (qmlImportTrace()) qDebug().nospace() << "QQmlImportDatabase::registerPluginTypes: " << uri << " from " << basePath; - - QQmlTypesExtensionInterface *iface = qobject_cast<QQmlTypesExtensionInterface *>(instance); - if (!iface) { - if (errors) { - QQmlError error; - error.setDescription(tr("Module loaded for URI '%1' does not implement QQmlTypesExtensionInterface").arg(typeNamespace)); - errors->prepend(error); - } - return false; - } - - const QByteArray bytes = uri.toUtf8(); - const char *moduleId = bytes.constData(); - - QQmlMetaTypeRegistrationFailureRecorder failureRecorder; - { - // Create a scope for QWriteLocker to keep it as narrow as possible, and - // to ensure that we release it before the call to initalizeEngine below - QMutexLocker lock(QQmlMetaType::typeRegistrationLock()); - - if (!typeNamespace.isEmpty()) { - // This is an 'identified' module - if (typeNamespace != uri) { - // The namespace for type registrations must match the URI for locating the module - if (errors) { - QQmlError error; - error.setDescription(tr("Module namespace '%1' does not match import URI '%2'").arg(typeNamespace).arg(uri)); - errors->prepend(error); - } - return false; - } - - if (QQmlMetaType::namespaceContainsRegistrations(typeNamespace, vmaj)) { - // Other modules have already installed to this namespace - if (errors) { - QQmlError error; - error.setDescription(tr("Namespace '%1' has already been used for type registration").arg(typeNamespace)); - errors->prepend(error); - } - return false; - } else { - QQmlMetaType::protectNamespace(typeNamespace); - } - } else { - // This is not an identified module - provide a warning - qWarning().nospace() << qPrintable(tr("Module '%1' does not contain a module identifier directive - it cannot be protected from external registrations.").arg(uri)); - } - - QQmlMetaType::setTypeRegistrationNamespace(typeNamespace); - - if (QQmlExtensionPlugin *plugin = qobject_cast<QQmlExtensionPlugin *>(instance)) { - // basepath should point to the directory of the module, not the plugin file itself: - QQmlExtensionPluginPrivate::get(plugin)->baseUrl = QQmlImports::urlFromLocalFileOrQrcOrUrl(basePath); - } - - iface->registerTypes(moduleId); - QQmlMetaType::setTypeRegistrationNamespace(QString()); - } // QWriteLocker lock(QQmlMetaType::typeRegistrationLock()) - - if (!failureRecorder.failures().isEmpty()) { - if (errors) { - for (const QString &failure : failureRecorder.failures()) { - QQmlError error; - error.setDescription(failure); - errors->prepend(error); - } - } - return false; - } - - return true; + return QQmlMetaType::registerPluginTypes(instance, basePath, uri, typeNamespace, vmaj, errors); } /*! @@ -2254,8 +2116,8 @@ bool QQmlImportDatabase::importDynamicPlugin(const QString &filePath, const QStr void QQmlImportDatabase::clearDirCache() { - QStringHash<QmldirCache *>::ConstIterator itr = qmldirCache.begin(); - while (itr != qmldirCache.end()) { + QStringHash<QmldirCache *>::ConstIterator itr = qmldirCache.constBegin(); + while (itr != qmldirCache.constEnd()) { QmldirCache *cache = *itr; do { QmldirCache *nextCache = cache->next; diff --git a/src/qml/qml/qqmlimport_p.h b/src/qml/qml/qqmlimport_p.h index f8c01ed876..d3055b552c 100644 --- a/src/qml/qml/qqmlimport_p.h +++ b/src/qml/qml/qqmlimport_p.h @@ -45,8 +45,8 @@ #include <QtCore/qset.h> #include <QtCore/qstringlist.h> #include <private/qqmldirparser_p.h> -#include <private/qqmlmetatype_p.h> -#include <private/qhashedstring_p.h> +#include <private/qqmltype_p.h> +#include <private/qstringhash_p.h> // // W A R N I N G diff --git a/src/qml/qml/qqmllist_p.h b/src/qml/qml/qqmllist_p.h index 04bab8d929..e182ced51d 100644 --- a/src/qml/qml/qqmllist_p.h +++ b/src/qml/qml/qqmllist_p.h @@ -52,7 +52,7 @@ // #include "qqmllist.h" -#include "qqmlpropertycache_p.h" +#include "qqmlmetaobject_p.h" QT_BEGIN_NAMESPACE 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 <private/qv8engine_p.h> #include <private/qqmllist_p.h> #include <private/qv4objectproto_p.h> #include <qv4objectiterator_p.h> diff --git a/src/qml/qml/qqmlmetaobject.cpp b/src/qml/qml/qqmlmetaobject.cpp new file mode 100644 index 0000000000..a967f46b12 --- /dev/null +++ b/src/qml/qml/qqmlmetaobject.cpp @@ -0,0 +1,327 @@ +/**************************************************************************** +** +** 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 "qqmlmetaobject_p.h" + +#include <private/qqmlengine_p.h> +#include <private/qqmlpropertycachemethodarguments_p.h> + +QT_BEGIN_NAMESPACE + +struct StaticQtMetaObject : public QObject +{ + static const QMetaObject *get() + { return &staticQtMetaObject; } +}; + +static bool isNamedEnumeratorInScope(const QMetaObject *resolvedMetaObject, const QByteArray &scope, + const QByteArray &name) +{ + for (int i = resolvedMetaObject->enumeratorCount() - 1; i >= 0; --i) { + QMetaEnum m = resolvedMetaObject->enumerator(i); + if ((m.name() == name) && (scope.isEmpty() || (m.scope() == scope))) + return true; + } + return false; +} + +static bool isNamedEnumerator(const QMetaObject *metaObj, const QByteArray &scopedName) +{ + QByteArray scope; + QByteArray name; + int scopeIdx = scopedName.lastIndexOf("::"); + if (scopeIdx != -1) { + scope = scopedName.left(scopeIdx); + name = scopedName.mid(scopeIdx + 2); + } else { + name = scopedName; + } + + if (scope == "Qt") + return isNamedEnumeratorInScope(StaticQtMetaObject::get(), scope, name); + + if (isNamedEnumeratorInScope(metaObj, scope, name)) + return true; + + if (metaObj->d.relatedMetaObjects && !scope.isEmpty()) { + for (auto related = metaObj->d.relatedMetaObjects; *related; ++related) { + if (isNamedEnumeratorInScope(*related, scope, name)) + return true; + } + } + + return false; +} + +// Returns true if \a from is assignable to a property of type \a to +bool QQmlMetaObject::canConvert(const QQmlMetaObject &from, const QQmlMetaObject &to) +{ + Q_ASSERT(!from.isNull() && !to.isNull()); + + struct I { static bool equal(const QMetaObject *lhs, const QMetaObject *rhs) { + return lhs == rhs || (lhs && rhs && lhs->d.stringdata == rhs->d.stringdata); + } }; + + const QMetaObject *tom = to._m.isT1()?to._m.asT1()->metaObject():to._m.asT2(); + if (tom == &QObject::staticMetaObject) return true; + + if (from._m.isT1() && to._m.isT1()) { // QQmlPropertyCache -> QQmlPropertyCache + QQmlPropertyCache *fromp = from._m.asT1(); + QQmlPropertyCache *top = to._m.asT1(); + + while (fromp) { + if (fromp == top) return true; + fromp = fromp->parent(); + } + } else if (from._m.isT1() && to._m.isT2()) { // QQmlPropertyCache -> QMetaObject + QQmlPropertyCache *fromp = from._m.asT1(); + + while (fromp) { + const QMetaObject *fromm = fromp->metaObject(); + if (fromm && I::equal(fromm, tom)) return true; + fromp = fromp->parent(); + } + } else if (from._m.isT2() && to._m.isT1()) { // QMetaObject -> QQmlPropertyCache + const QMetaObject *fromm = from._m.asT2(); + + if (!tom) return false; + + while (fromm) { + if (I::equal(fromm, tom)) return true; + fromm = fromm->superClass(); + } + } else { // QMetaObject -> QMetaObject + const QMetaObject *fromm = from._m.asT2(); + + while (fromm) { + if (I::equal(fromm, tom)) return true; + fromm = fromm->superClass(); + } + } + + return false; +} + +void QQmlMetaObject::resolveGadgetMethodOrPropertyIndex(QMetaObject::Call type, const QMetaObject **metaObject, int *index) +{ + int offset; + + switch (type) { + case QMetaObject::ReadProperty: + case QMetaObject::WriteProperty: + case QMetaObject::ResetProperty: + case QMetaObject::QueryPropertyDesignable: + case QMetaObject::QueryPropertyEditable: + case QMetaObject::QueryPropertyScriptable: + case QMetaObject::QueryPropertyStored: + case QMetaObject::QueryPropertyUser: + offset = (*metaObject)->propertyOffset(); + while (*index < offset) { + *metaObject = (*metaObject)->superClass(); + offset = (*metaObject)->propertyOffset(); + } + break; + case QMetaObject::InvokeMetaMethod: + offset = (*metaObject)->methodOffset(); + while (*index < offset) { + *metaObject = (*metaObject)->superClass(); + offset = (*metaObject)->methodOffset(); + } + break; + default: + offset = 0; + Q_UNIMPLEMENTED(); + offset = INT_MAX; + } + + *index -= offset; +} + +QQmlPropertyCache *QQmlMetaObject::propertyCache(QQmlEnginePrivate *e) const +{ + if (_m.isNull()) return nullptr; + if (_m.isT1()) return _m.asT1(); + else return e->cache(_m.asT2()); +} + +int QQmlMetaObject::methodReturnType(const QQmlPropertyData &data, QByteArray *unknownTypeError) const +{ + Q_ASSERT(!_m.isNull() && data.coreIndex() >= 0); + + int type = data.propType(); + + const char *propTypeName = nullptr; + + if (type == QMetaType::UnknownType) { + // Find the return type name from the method info + QMetaMethod m; + + if (_m.isT1()) { + QQmlPropertyCache *c = _m.asT1(); + Q_ASSERT(data.coreIndex() < c->methodIndexCacheStart + c->methodIndexCache.count()); + + while (data.coreIndex() < c->methodIndexCacheStart) + c = c->_parent; + + const QMetaObject *metaObject = c->createMetaObject(); + Q_ASSERT(metaObject); + m = metaObject->method(data.coreIndex()); + } else { + m = _m.asT2()->method(data.coreIndex()); + } + + type = m.returnType(); + propTypeName = m.typeName(); + } + + if (QMetaType::sizeOf(type) <= int(sizeof(int))) { + if (QMetaType::typeFlags(type) & QMetaType::IsEnumeration) + return QMetaType::Int; + + if (isNamedEnumerator(metaObject(), propTypeName)) + return QMetaType::Int; + + if (type == QMetaType::UnknownType) { + if (unknownTypeError) + *unknownTypeError = propTypeName; + } + } // else we know that it's a known type, as sizeOf(UnknownType) == 0 + + return type; +} + +int *QQmlMetaObject::methodParameterTypes(int index, ArgTypeStorage *argStorage, + QByteArray *unknownTypeError) const +{ + Q_ASSERT(!_m.isNull() && index >= 0); + + if (_m.isT1()) { + typedef QQmlPropertyCacheMethodArguments A; + + QQmlPropertyCache *c = _m.asT1(); + Q_ASSERT(index < c->methodIndexCacheStart + c->methodIndexCache.count()); + + while (index < c->methodIndexCacheStart) + c = c->_parent; + + QQmlPropertyData *rv = const_cast<QQmlPropertyData *>(&c->methodIndexCache.at(index - c->methodIndexCacheStart)); + + if (rv->arguments() && static_cast<A *>(rv->arguments())->argumentsValid) + return static_cast<A *>(rv->arguments())->arguments; + + const QMetaObject *metaObject = c->createMetaObject(); + Q_ASSERT(metaObject); + QMetaMethod m = metaObject->method(index); + + int argc = m.parameterCount(); + if (!rv->arguments()) { + A *args = c->createArgumentsObject(argc, m.parameterNames()); + rv->setArguments(args); + } + A *args = static_cast<A *>(rv->arguments()); + + QList<QByteArray> argTypeNames; // Only loaded if needed + + for (int ii = 0; ii < argc; ++ii) { + int type = m.parameterType(ii); + + if (QMetaType::sizeOf(type) > int(sizeof(int))) { + // Cannot be passed as int + // We know that it's a known type, as sizeOf(UnknownType) == 0 + } else if (QMetaType::typeFlags(type) & QMetaType::IsEnumeration) { + type = QMetaType::Int; + } else { + if (argTypeNames.isEmpty()) + argTypeNames = m.parameterTypes(); + if (isNamedEnumerator(metaObject, argTypeNames.at(ii))) { + type = QMetaType::Int; + } else if (type == QMetaType::UnknownType){ + if (unknownTypeError) + *unknownTypeError = argTypeNames.at(ii); + return nullptr; + } + + } + args->arguments[ii + 1] = type; + } + args->argumentsValid = true; + return static_cast<A *>(rv->arguments())->arguments; + + } else { + QMetaMethod m = _m.asT2()->method(index); + return methodParameterTypes(m, argStorage, unknownTypeError); + + } +} + +int *QQmlMetaObject::methodParameterTypes(const QMetaMethod &m, ArgTypeStorage *argStorage, + QByteArray *unknownTypeError) const +{ + Q_ASSERT(argStorage); + + int argc = m.parameterCount(); + argStorage->resize(argc + 1); + argStorage->operator[](0) = argc; + QList<QByteArray> argTypeNames; // Only loaded if needed + + for (int ii = 0; ii < argc; ++ii) { + int type = m.parameterType(ii); + if (QMetaType::sizeOf(type) > int(sizeof(int))) { + // Cannot be passed as int + // We know that it's a known type, as sizeOf(UnknownType) == 0 + } else if (QMetaType::typeFlags(type) & QMetaType::IsEnumeration) { + type = QMetaType::Int; + } else { + if (argTypeNames.isEmpty()) + argTypeNames = m.parameterTypes(); + if (isNamedEnumerator(_m.asT2(), argTypeNames.at(ii))) { + type = QMetaType::Int; + } else if (type == QMetaType::UnknownType) { + if (unknownTypeError) + *unknownTypeError = argTypeNames.at(ii); + return nullptr; + } + } + argStorage->operator[](ii + 1) = type; + } + + return argStorage->data(); +} + +QT_END_NAMESPACE diff --git a/src/qml/qml/qqmlmetaobject_p.h b/src/qml/qml/qqmlmetaobject_p.h new file mode 100644 index 0000000000..65d6361b90 --- /dev/null +++ b/src/qml/qml/qqmlmetaobject_p.h @@ -0,0 +1,187 @@ +/**************************************************************************** +** +** 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 QQMLMETAOBJECT_P_H +#define QQMLMETAOBJECT_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 <QtQml/qtqmlglobal.h> + +#include <private/qflagpointer_p.h> +#include <private/qqmlpropertycache_p.h> + +#include <QtCore/qvarlengtharray.h> +#include <QtCore/qmetaobject.h> + +QT_BEGIN_NAMESPACE + +// QQmlMetaObject serves as a wrapper around either QMetaObject or QQmlPropertyCache. +// This is necessary as we delay creation of QMetaObject for synthesized QObjects, but +// we don't want to needlessly generate QQmlPropertyCaches every time we encounter a +// QObject type used in assignment or when we don't have a QQmlEngine etc. +// +// This class does NOT reference the propertycache. +class QQmlEnginePrivate; +class QQmlPropertyData; +class Q_QML_EXPORT QQmlMetaObject +{ +public: + typedef QVarLengthArray<int, 9> ArgTypeStorage; + + inline QQmlMetaObject(); + inline QQmlMetaObject(QObject *); + inline QQmlMetaObject(const QMetaObject *); + inline QQmlMetaObject(QQmlPropertyCache *); + inline QQmlMetaObject(const QQmlMetaObject &); + + inline QQmlMetaObject &operator=(const QQmlMetaObject &); + + inline bool isNull() const; + + inline const char *className() const; + inline int propertyCount() const; + + inline bool hasMetaObject() const; + inline const QMetaObject *metaObject() const; + + QQmlPropertyCache *propertyCache(QQmlEnginePrivate *) const; + + int methodReturnType(const QQmlPropertyData &data, QByteArray *unknownTypeError) const; + int *methodParameterTypes(int index, ArgTypeStorage *argStorage, + QByteArray *unknownTypeError) const; + + static bool canConvert(const QQmlMetaObject &from, const QQmlMetaObject &to); + + // static_metacall (on Gadgets) doesn't call the base implementation and therefore + // we need a helper to find the correct meta object and property/method index. + static void resolveGadgetMethodOrPropertyIndex(QMetaObject::Call type, const QMetaObject **metaObject, int *index); + +protected: + QBiPointer<QQmlPropertyCache, const QMetaObject> _m; + int *methodParameterTypes(const QMetaMethod &method, ArgTypeStorage *argStorage, + QByteArray *unknownTypeError) const; + +}; + +QQmlMetaObject::QQmlMetaObject() +{ +} + +QQmlMetaObject::QQmlMetaObject(QObject *o) +{ + if (o) { + QQmlData *ddata = QQmlData::get(o, false); + if (ddata && ddata->propertyCache) _m = ddata->propertyCache; + else _m = o->metaObject(); + } +} + +QQmlMetaObject::QQmlMetaObject(const QMetaObject *m) + : _m(m) +{ +} + +QQmlMetaObject::QQmlMetaObject(QQmlPropertyCache *m) + : _m(m) +{ +} + +QQmlMetaObject::QQmlMetaObject(const QQmlMetaObject &o) + : _m(o._m) +{ +} + +QQmlMetaObject &QQmlMetaObject::operator=(const QQmlMetaObject &o) +{ + _m = o._m; + return *this; +} + +bool QQmlMetaObject::isNull() const +{ + return _m.isNull(); +} + +const char *QQmlMetaObject::className() const +{ + if (_m.isNull()) { + return nullptr; + } else if (_m.isT1()) { + return _m.asT1()->className(); + } else { + return _m.asT2()->className(); + } +} + +int QQmlMetaObject::propertyCount() const +{ + if (_m.isNull()) { + return 0; + } else if (_m.isT1()) { + return _m.asT1()->propertyCount(); + } else { + return _m.asT2()->propertyCount(); + } +} + +bool QQmlMetaObject::hasMetaObject() const +{ + return _m.isT2() || (!_m.isNull() && _m.asT1()->metaObject()); +} + +const QMetaObject *QQmlMetaObject::metaObject() const +{ + if (_m.isNull()) return nullptr; + if (_m.isT1()) return _m.asT1()->createMetaObject(); + else return _m.asT2(); +} + +QT_END_NAMESPACE + +#endif // QQMLMETAOBJECT_P_H diff --git a/src/qml/qml/qqmlmetatype.cpp b/src/qml/qml/qqmlmetatype.cpp index a8ec5d18f8..0da96f61e4 100644 --- a/src/qml/qml/qqmlmetatype.cpp +++ b/src/qml/qml/qqmlmetatype.cpp @@ -37,411 +37,71 @@ ** ****************************************************************************/ -#include <QtQml/qqmlprivate.h> #include "qqmlmetatype_p.h" -#include <private/qqmlproxymetaobject_p.h> -#include <private/qqmlcustomparser_p.h> -#include <private/qhashedstring_p.h> -#include <private/qqmlimport_p.h> - -#include <QtCore/qdebug.h> -#include <QtCore/qstringlist.h> -#include <QtCore/qmetaobject.h> -#include <QtCore/qbitarray.h> -#include <QtCore/qreadwritelock.h> -#include <QtCore/private/qmetaobject_p.h> -#include <QtCore/qloggingcategory.h> - -#include <qmetatype.h> -#include <qobjectdefs.h> -#include <qbytearray.h> -#include <qreadwritelock.h> -#include <qstring.h> -#include <qstringlist.h> -#include <qvector.h> +#include <private/qqmlmetatypedata_p.h> +#include <private/qqmltypemodule_p_p.h> +#include <private/qqmltype_p_p.h> +#include <private/qqmltypeloader_p.h> +#include <private/qqmlextensionplugin_p.h> -#include <ctype.h> -#include "qqmlcomponent.h" +#include <QtCore/qcoreapplication.h> +#include <QtCore/qmutex.h> +#include <QtCore/qloggingcategory.h> Q_DECLARE_LOGGING_CATEGORY(DBG_DISK_CACHE) QT_BEGIN_NAMESPACE -struct QQmlMetaTypeData +struct LockedData : private QQmlMetaTypeData { - QQmlMetaTypeData(); - ~QQmlMetaTypeData(); - void registerType(QQmlTypePrivate *priv); - QList<QQmlType> types; - QSet<QQmlType> undeletableTypes; - typedef QHash<int, QQmlTypePrivate *> Ids; - Ids idToType; - typedef QHash<QHashedStringRef, QQmlTypePrivate *> Names; - Names nameToType; - typedef QHash<QUrl, QQmlTypePrivate *> Files; //For file imported composite types only - Files urlToType; - Files urlToNonFileImportType; // For non-file imported composite and composite - // singleton types. This way we can locate any - // of them by url, even if it was registered as - // a module via QQmlPrivate::RegisterCompositeType - typedef QHash<const QMetaObject *, QQmlTypePrivate *> MetaObjects; - MetaObjects metaObjectToType; - typedef QHash<int, QQmlMetaType::StringConverter> StringConverters; - StringConverters stringConverters; - - struct VersionedUri { - VersionedUri() - : majorVersion(0) {} - VersionedUri(const QHashedString &uri, int majorVersion) - : uri(uri), majorVersion(majorVersion) {} - bool operator==(const VersionedUri &other) const { - return other.majorVersion == majorVersion && other.uri == uri; - } - QHashedString uri; - int majorVersion; - }; - typedef QHash<VersionedUri, QQmlTypeModule *> TypeModules; - TypeModules uriToModule; - - QBitArray objects; - QBitArray interfaces; - QBitArray lists; - - QList<QQmlPrivate::AutoParentFunction> parentFunctions; - QVector<QQmlPrivate::QmlUnitCacheLookupFunction> lookupCachedQmlUnit; - - QSet<QString> protectedNamespaces; - - QString typeRegistrationNamespace; - - QHash<int, int> qmlLists; - - QHash<const QMetaObject *, QQmlPropertyCache *> propertyCaches; - QQmlPropertyCache *propertyCache(const QMetaObject *metaObject, int minorVersion); - QQmlPropertyCache *propertyCache(const QQmlType &type, int minorVersion); - - void startRecordingTypeRegFailures(QStringList *storage) - { typeRegistrationFailures = storage; } - void stopRecordingTypeRegFailures() - { startRecordingTypeRegFailures(nullptr); } - void recordTypeRegFailure(const QString &message) - { - if (typeRegistrationFailures) - typeRegistrationFailures->append(message); - else - qWarning("%s", message.toUtf8().constData()); - } - -private: - QStringList *typeRegistrationFailures = nullptr; -}; - -struct EnumInfo { - QStringList path; - QString metaObjectName; - QString enumName; - QString enumKey; - QString metaEnumScope; - bool scoped; -}; - -class QQmlTypeModulePrivate -{ -public: - QQmlTypeModulePrivate() - : minMinorVersion(INT_MAX), maxMinorVersion(0), locked(false) {} - - static QQmlTypeModulePrivate* get(QQmlTypeModule* q) { return q->d; } - - QQmlMetaTypeData::VersionedUri uri; - - int minMinorVersion; - int maxMinorVersion; - bool locked; - - void add(QQmlTypePrivate *); - void remove(const QQmlTypePrivate *type); - - typedef QStringHash<QList<QQmlTypePrivate *> > TypeHash; - TypeHash typeHash; + friend class QQmlMetaTypeDataPtr; }; -Q_GLOBAL_STATIC(QQmlMetaTypeData, metaTypeData) +Q_GLOBAL_STATIC(LockedData, metaTypeData) Q_GLOBAL_STATIC_WITH_ARGS(QMutex, metaTypeDataLock, (QMutex::Recursive)) -static uint qHash(const QQmlMetaTypeData::VersionedUri &v) -{ - return v.uri.hash() ^ qHash(v.majorVersion); -} - -QQmlMetaTypeRegistrationFailureRecorder::QQmlMetaTypeRegistrationFailureRecorder() -{ - metaTypeData()->startRecordingTypeRegFailures(&_failures); -} - -QQmlMetaTypeRegistrationFailureRecorder::~QQmlMetaTypeRegistrationFailureRecorder() +class QQmlMetaTypeDataPtr { - metaTypeData()->stopRecordingTypeRegFailures(); -} - -QQmlMetaTypeData::QQmlMetaTypeData() -{ -} - -QQmlMetaTypeData::~QQmlMetaTypeData() -{ - for (TypeModules::const_iterator i = uriToModule.constBegin(), cend = uriToModule.constEnd(); i != cend; ++i) - delete *i; - for (QHash<const QMetaObject *, QQmlPropertyCache *>::Iterator it = propertyCaches.begin(), end = propertyCaches.end(); - it != end; ++it) - (*it)->release(); -} - -class QQmlTypePrivate -{ - Q_DISABLE_COPY(QQmlTypePrivate) + Q_DISABLE_COPY_MOVE(QQmlMetaTypeDataPtr) public: - QQmlTypePrivate(QQmlType::RegistrationType type); - ~QQmlTypePrivate(); - - void init() const; - void initEnums(const QQmlPropertyCache *cache = nullptr) const; - void insertEnums(const QMetaObject *metaObject) const; - void insertEnumsFromPropertyCache(const QQmlPropertyCache *cache) const; + QQmlMetaTypeDataPtr() : locker(metaTypeDataLock()), data(metaTypeData()) {} + ~QQmlMetaTypeDataPtr() = default; - QAtomicInt refCount; - QQmlType::RegistrationType regType; + QQmlMetaTypeData &operator*() { return *data; } + QQmlMetaTypeData *operator->() { return data; } + operator QQmlMetaTypeData *() { return data; } - struct QQmlCppTypeData - { - int allocationSize; - void (*newFunc)(void *); - QString noCreationReason; - int parserStatusCast; - QObject *(*extFunc)(QObject *); - const QMetaObject *extMetaObject; - QQmlCustomParser *customParser; - QQmlAttachedPropertiesFunc attachedPropertiesFunc; - const QMetaObject *attachedPropertiesType; - int attachedPropertiesId; - int propertyValueSourceCast; - int propertyValueInterceptorCast; - bool registerEnumClassesUnscoped; - }; - - struct QQmlSingletonTypeData - { - QQmlType::SingletonInstanceInfo *singletonInstanceInfo; - }; + const QQmlMetaTypeData &operator*() const { return *data; } + const QQmlMetaTypeData *operator->() const { return data; } + operator const QQmlMetaTypeData *() const { return data; } - struct QQmlCompositeTypeData - { - QUrl url; - }; - - union extraData { - QQmlCppTypeData* cd; - QQmlSingletonTypeData* sd; - QQmlCompositeTypeData* fd; - } extraData; - - const char *iid; - QHashedString module; - QString name; - QString elementName; - int version_maj; - int version_min; - int typeId; - int listId; - int revision; - mutable bool containsRevisionedAttributes; - mutable QQmlType superType; - const QMetaObject *baseMetaObject; - - int index; - mutable volatile bool isSetup:1; - mutable volatile bool isEnumFromCacheSetup:1; - mutable volatile bool isEnumFromBaseSetup:1; - mutable bool haveSuperType:1; - mutable QList<QQmlProxyMetaObject::ProxyData> metaObjects; - mutable QStringHash<int> enums; - mutable QStringHash<int> scopedEnumIndex; // maps from enum name to index in scopedEnums - mutable QList<QStringHash<int>*> scopedEnums; - - static QHash<const QMetaObject *, int> attachedPropertyIds; - - struct PropertyCacheByMinorVersion - { - PropertyCacheByMinorVersion() : cache(nullptr), minorVersion(-1) {} - explicit PropertyCacheByMinorVersion(QQmlPropertyCache *pc, int ver) : cache(pc), minorVersion(ver) {} - QQmlPropertyCachePtr cache; - int minorVersion; - }; - QVector<PropertyCacheByMinorVersion> propertyCaches; - QQmlPropertyCache *propertyCacheForMinorVersion(int minorVersion) const; - void setPropertyCacheForMinorVersion(int minorVersion, QQmlPropertyCache *cache); private: - void createListOfPossibleConflictingItems(const QMetaObject *metaObject, QList<EnumInfo> &enumInfoList, QStringList path) const; - void createEnumConflictReport(const QMetaObject *metaObject, const QString &conflictingKey) const; + QMutexLocker locker; + LockedData *data = nullptr; }; -void QQmlMetaTypeData::registerType(QQmlTypePrivate *priv) -{ - for (int i = 0; i < types.count(); ++i) { - if (!types.at(i).isValid()) { - types[i] = QQmlType(priv); - priv->index = i; - return; - } - } - types.append(QQmlType(priv)); - priv->index = types.count() - 1; -} - -void QQmlType::SingletonInstanceInfo::init(QQmlEngine *e) -{ - if (scriptCallback && scriptApi(e).isUndefined()) { - QJSValue value = scriptCallback(e, e); - if (value.isQObject()) { - QObject *o = value.toQObject(); - // even though the object is defined in C++, qmlContext(obj) and qmlEngine(obj) - // should behave identically to QML singleton types. - e->setContextForObject(o, new QQmlContext(e->rootContext(), e)); - } - setScriptApi(e, value); - } else if (qobjectCallback && !qobjectApi(e)) { - QObject *o = qobjectCallback(e, e); - setQObjectApi(e, o); - if (!o) { - qFatal("qmlRegisterSingletonType(): \"%s\" is not available because the callback function returns a null pointer.", qPrintable(typeName)); - } - // if this object can use a property cache, create it now - QQmlData::ensurePropertyCache(e, o); - // even though the object is defined in C++, qmlContext(obj) and qmlEngine(obj) - // should behave identically to QML singleton types. - e->setContextForObject(o, new QQmlContext(e->rootContext(), e)); - } else if (!url.isEmpty() && !qobjectApi(e)) { - QQmlComponent component(e, url, QQmlComponent::PreferSynchronous); - QObject *o = component.beginCreate(e->rootContext()); - setQObjectApi(e, o); - if (o) - component.completeCreate(); - } -} - -void QQmlType::SingletonInstanceInfo::destroy(QQmlEngine *e) -{ - // cleans up the engine-specific singleton instances if they exist. - scriptApis.remove(e); - QObject *o = qobjectApis.take(e); - if (o) { - QQmlData *ddata = QQmlData::get(o, false); - if (url.isEmpty() && ddata && ddata->indestructible && ddata->explicitIndestructibleSet) - return; - delete o; - } -} - -void QQmlType::SingletonInstanceInfo::setQObjectApi(QQmlEngine *e, QObject *o) -{ - qobjectApis.insert(e, o); -} - -QObject *QQmlType::SingletonInstanceInfo::qobjectApi(QQmlEngine *e) const -{ - return qobjectApis.value(e); -} - -void QQmlType::SingletonInstanceInfo::setScriptApi(QQmlEngine *e, const QJSValue &v) +static QQmlTypePrivate *createQQmlType(QQmlMetaTypeData *data, + const QQmlPrivate::RegisterInterface &type) { - scriptApis.insert(e, v); -} - -QJSValue QQmlType::SingletonInstanceInfo::scriptApi(QQmlEngine *e) const -{ - return scriptApis.value(e); -} - -QHash<const QMetaObject *, int> 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), isEnumFromCacheSetup(false), isEnumFromBaseSetup(false), - haveSuperType(false) -{ - switch (type) { - case QQmlType::CppType: - extraData.cd = new QQmlCppTypeData; - extraData.cd->allocationSize = 0; - extraData.cd->newFunc = nullptr; - extraData.cd->parserStatusCast = -1; - extraData.cd->extFunc = nullptr; - extraData.cd->extMetaObject = nullptr; - extraData.cd->customParser = nullptr; - extraData.cd->attachedPropertiesFunc = nullptr; - extraData.cd->attachedPropertiesType = nullptr; - extraData.cd->propertyValueSourceCast = -1; - extraData.cd->propertyValueInterceptorCast = -1; - extraData.cd->registerEnumClassesUnscoped = true; - break; - case QQmlType::SingletonType: - case QQmlType::CompositeSingletonType: - extraData.sd = new QQmlSingletonTypeData; - extraData.sd->singletonInstanceInfo = nullptr; - break; - case QQmlType::InterfaceType: - extraData.cd = nullptr; - break; - case QQmlType::CompositeType: - extraData.fd = new QQmlCompositeTypeData; - break; - default: qFatal("QQmlTypePrivate Internal Error."); - } -} - -QQmlTypePrivate::~QQmlTypePrivate() -{ - qDeleteAll(scopedEnums); - switch (regType) { - case QQmlType::CppType: - delete extraData.cd->customParser; - delete extraData.cd; - break; - case QQmlType::SingletonType: - case QQmlType::CompositeSingletonType: - delete extraData.sd->singletonInstanceInfo; - delete extraData.sd; - break; - case QQmlType::CompositeType: - delete extraData.fd; - break; - default: //Also InterfaceType, because it has no extra data - break; - } -} - -QQmlType::QQmlType(QQmlMetaTypeData *data, const QQmlPrivate::RegisterInterface &interface) - : d(new QQmlTypePrivate(InterfaceType)) -{ - d->iid = interface.iid; - d->typeId = interface.typeId; - d->listId = interface.listId; + auto *d = new QQmlTypePrivate(QQmlType::InterfaceType); + d->iid = type.iid; + d->typeId = type.typeId; + d->listId = type.listId; d->isSetup = true; d->version_maj = 0; d->version_min = 0; data->registerType(d); + return d; } -QQmlType::QQmlType(QQmlMetaTypeData *data, const QString &elementName, const QQmlPrivate::RegisterSingletonType &type) - : d(new QQmlTypePrivate(SingletonType)) +static QQmlTypePrivate *createQQmlType(QQmlMetaTypeData *data, const QString &elementName, + const QQmlPrivate::RegisterSingletonType &type) { + auto *d = new QQmlTypePrivate(QQmlType::SingletonType); data->registerType(d); - d->elementName = elementName; - d->module = QString::fromUtf8(type.uri); - + d->setName(QString::fromUtf8(type.uri), elementName); d->version_maj = type.versionMajor; d->version_min = type.versionMinor; @@ -454,37 +114,22 @@ QQmlType::QQmlType(QQmlMetaTypeData *data, const QString &elementName, const QQm d->revision = type.revision; } - d->extraData.sd->singletonInstanceInfo = new SingletonInstanceInfo; + d->extraData.sd->singletonInstanceInfo = new QQmlType::SingletonInstanceInfo; d->extraData.sd->singletonInstanceInfo->scriptCallback = type.scriptApi; d->extraData.sd->singletonInstanceInfo->qobjectCallback = type.qobjectApi; d->extraData.sd->singletonInstanceInfo->typeName = QString::fromUtf8(type.typeName); d->extraData.sd->singletonInstanceInfo->instanceMetaObject - = (type.qobjectApi && type.version >= 1) ? type.instanceMetaObject : nullptr; -} - -QQmlType::QQmlType(QQmlMetaTypeData *data, const QString &elementName, const QQmlPrivate::RegisterCompositeSingletonType &type) - : d(new QQmlTypePrivate(CompositeSingletonType)) -{ - data->registerType(d); - - d->elementName = elementName; - d->module = QString::fromUtf8(type.uri); - - d->version_maj = type.versionMajor; - d->version_min = type.versionMinor; + = (type.qobjectApi && type.version >= 1) ? type.instanceMetaObject : nullptr; - d->extraData.sd->singletonInstanceInfo = new SingletonInstanceInfo; - d->extraData.sd->singletonInstanceInfo->url = QQmlTypeLoader::normalize(type.url); - d->extraData.sd->singletonInstanceInfo->typeName = QString::fromUtf8(type.typeName); + return d; } -QQmlType::QQmlType(QQmlMetaTypeData *data, const QString &elementName, const QQmlPrivate::RegisterType &type) - : d(new QQmlTypePrivate(CppType)) +static QQmlTypePrivate *createQQmlType(QQmlMetaTypeData *data, const QString &elementName, + const QQmlPrivate::RegisterType &type) { + QQmlTypePrivate *d = new QQmlTypePrivate(QQmlType::CppType); data->registerType(d); - - d->elementName = elementName; - d->module = QString::fromUtf8(type.uri); + d->setName(QString::fromUtf8(type.uri), elementName); d->version_maj = type.versionMajor; d->version_min = type.versionMinor; @@ -499,10 +144,8 @@ QQmlType::QQmlType(QQmlMetaTypeData *data, const QString &elementName, const QQm d->extraData.cd->attachedPropertiesFunc = type.attachedPropertiesFunction; d->extraData.cd->attachedPropertiesType = type.attachedPropertiesMetaObject; if (d->extraData.cd->attachedPropertiesType) { - auto iter = QQmlTypePrivate::attachedPropertyIds.find(d->baseMetaObject); - if (iter == QQmlTypePrivate::attachedPropertyIds.end()) - iter = QQmlTypePrivate::attachedPropertyIds.insert(d->baseMetaObject, d->index); - d->extraData.cd->attachedPropertiesId = *iter; + d->extraData.cd->attachedPropertiesId = data->attachedPropertyId(d->baseMetaObject, + d->index); } else { d->extraData.cd->attachedPropertiesId = -1; } @@ -522,149 +165,41 @@ QQmlType::QQmlType(QQmlMetaTypeData *data, const QString &elementName, const QQm if (indexOfClassInfo != -1 && QString::fromUtf8(d->baseMetaObject->classInfo(indexOfClassInfo).value()) == QLatin1String("false")) d->extraData.cd->registerEnumClassesUnscoped = false; } + + return d; } -QQmlType::QQmlType(QQmlMetaTypeData *data, const QString &elementName, const QQmlPrivate::RegisterCompositeType &type) - : d(new QQmlTypePrivate(CompositeType)) +static QQmlTypePrivate *createQQmlType(QQmlMetaTypeData *data, const QString &elementName, + const QQmlPrivate::RegisterCompositeType &type) { + auto *d = new QQmlTypePrivate(QQmlType::CompositeType); data->registerType(d); - - d->elementName = elementName; - - d->module = QString::fromUtf8(type.uri); + d->setName(QString::fromUtf8(type.uri), elementName); d->version_maj = type.versionMajor; d->version_min = type.versionMinor; d->extraData.fd->url = QQmlTypeLoader::normalize(type.url); + return d; } -QQmlType::QQmlType() - : d(nullptr) -{ -} - -QQmlType::QQmlType(const QQmlType &other) - : d(other.d) -{ - if (d) - d->refCount.ref(); -} - -QQmlType &QQmlType::operator =(const QQmlType &other) -{ - if (d != other.d) { - if (d && !d->refCount.deref()) - delete d; - d = other.d; - if (d) - d->refCount.ref(); - } - return *this; -} - -QQmlType::QQmlType(QQmlTypePrivate *priv) - : d(priv) -{ - if (d) - d->refCount.ref(); -} - -QQmlType::~QQmlType() -{ - if (d && !d->refCount.deref()) { - // If attached properties were successfully registered, deregister them. - // (They may not have been registered if some other type used the same baseMetaObject) - if (d->regType == CppType && d->extraData.cd->attachedPropertiesType) { - auto it = QQmlTypePrivate::attachedPropertyIds.find(d->baseMetaObject); - if (it != QQmlTypePrivate::attachedPropertyIds.end() && *it == d->index) - QQmlTypePrivate::attachedPropertyIds.erase(it); - } - delete d; - } -} - -QHashedString QQmlType::module() const -{ - if (!d) - return QHashedString(); - return d->module; -} - -int QQmlType::majorVersion() const +static QQmlTypePrivate *createQQmlType(QQmlMetaTypeData *data, const QString &elementName, + const QQmlPrivate::RegisterCompositeSingletonType &type) { - if (!d) - return -1; - return d->version_maj; -} - -int QQmlType::minorVersion() const -{ - if (!d) - return -1; - return d->version_min; -} - -bool QQmlType::availableInVersion(int vmajor, int vminor) const -{ - Q_ASSERT(vmajor >= 0 && vminor >= 0); - if (!d) - return false; - return vmajor == d->version_maj && vminor >= d->version_min; -} - -bool QQmlType::availableInVersion(const QHashedStringRef &module, int vmajor, int vminor) const -{ - Q_ASSERT(vmajor >= 0 && vminor >= 0); - if (!d) - return false; - return module == d->module && vmajor == d->version_maj && vminor >= d->version_min; -} - -// returns the nearest _registered_ super class -QQmlType QQmlType::superType() const -{ - if (!d) - return QQmlType(); - if (!d->haveSuperType && d->baseMetaObject) { - const QMetaObject *mo = d->baseMetaObject->superClass(); - while (mo && !d->superType.isValid()) { - d->superType = QQmlMetaType::qmlType(mo, d->module, d->version_maj, d->version_min); - mo = mo->superClass(); - } - d->haveSuperType = true; - } - - return d->superType; -} + auto *d = new QQmlTypePrivate(QQmlType::CompositeSingletonType); + data->registerType(d); + d->setName(QString::fromUtf8(type.uri), elementName); -QQmlType QQmlType::resolveCompositeBaseType(QQmlEnginePrivate *engine) const -{ - Q_ASSERT(isComposite()); - if (!engine || !d) - return QQmlType(); - QQmlRefPointer<QQmlTypeData> td(engine->typeLoader.getType(sourceUrl())); - if (td.isNull() || !td->isComplete()) - return QQmlType(); - QV4::CompiledData::CompilationUnit *compilationUnit = td->compilationUnit(); - const QMetaObject *mo = compilationUnit->rootPropertyCache()->firstCppMetaObject(); - return QQmlMetaType::qmlType(mo); -} + d->version_maj = type.versionMajor; + d->version_min = type.versionMinor; -QQmlPropertyCache *QQmlType::compositePropertyCache(QQmlEnginePrivate *engine) const -{ - // similar logic to resolveCompositeBaseType - Q_ASSERT(isComposite()); - if (!engine) - return nullptr; - QQmlRefPointer<QQmlTypeData> td(engine->typeLoader.getType(sourceUrl())); - if (td.isNull() || !td->isComplete()) - return nullptr; - QV4::CompiledData::CompilationUnit *compilationUnit = td->compilationUnit(); - return compilationUnit->rootPropertyCache().data(); + d->extraData.sd->singletonInstanceInfo = new QQmlType::SingletonInstanceInfo; + d->extraData.sd->singletonInstanceInfo->url = QQmlTypeLoader::normalize(type.url); + d->extraData.sd->singletonInstanceInfo->typeName = QString::fromUtf8(type.typeName); + return d; } -static void clone(QMetaObjectBuilder &builder, const QMetaObject *mo, - const QMetaObject *ignoreStart, const QMetaObject *ignoreEnd) +void QQmlMetaType::clone(QMetaObjectBuilder &builder, const QMetaObject *mo, + const QMetaObject *ignoreStart, const QMetaObject *ignoreEnd) { // Set classname builder.setClassName(ignoreEnd->className()); @@ -731,918 +266,10 @@ static void clone(QMetaObjectBuilder &builder, const QMetaObject *mo, } } -static bool isPropertyRevisioned(const QMetaObject *mo, int index) -{ - int i = index; - i -= mo->propertyOffset(); - if (i < 0 && mo->d.superdata) - return isPropertyRevisioned(mo->d.superdata, index); - - const QMetaObjectPrivate *mop = reinterpret_cast<const QMetaObjectPrivate*>(mo->d.data); - if (i >= 0 && i < mop->propertyCount) { - int handle = mop->propertyData + 3*i; - int flags = mo->d.data[handle + 2]; - - return (flags & Revisioned); - } - - return false; -} - -void QQmlTypePrivate::init() const -{ - if (isSetup) - return; - - QMutexLocker lock(metaTypeDataLock()); - if (isSetup) - return; - - const QMetaObject *mo = baseMetaObject; - if (!mo) { - // version 0 singleton type without metaobject information - return; - } - - if (regType == QQmlType::CppType) { - // Setup extended meta object - // XXX - very inefficient - if (extraData.cd->extFunc) { - QMetaObjectBuilder builder; - clone(builder, extraData.cd->extMetaObject, extraData.cd->extMetaObject, extraData.cd->extMetaObject); - builder.setFlags(QMetaObjectBuilder::DynamicMetaObject); - QMetaObject *mmo = builder.toMetaObject(); - mmo->d.superdata = mo; - QQmlProxyMetaObject::ProxyData data = { mmo, extraData.cd->extFunc, 0, 0 }; - metaObjects << data; - } - } - - mo = mo->d.superdata; - while(mo) { - QQmlTypePrivate *t = metaTypeData()->metaObjectToType.value(mo); - if (t) { - if (t->regType == QQmlType::CppType) { - if (t->extraData.cd->extFunc) { - QMetaObjectBuilder builder; - clone(builder, t->extraData.cd->extMetaObject, t->baseMetaObject, baseMetaObject); - builder.setFlags(QMetaObjectBuilder::DynamicMetaObject); - QMetaObject *mmo = builder.toMetaObject(); - mmo->d.superdata = baseMetaObject; - if (!metaObjects.isEmpty()) - metaObjects.constLast().metaObject->d.superdata = mmo; - QQmlProxyMetaObject::ProxyData data = { mmo, t->extraData.cd->extFunc, 0, 0 }; - metaObjects << data; - } - } - } - mo = mo->d.superdata; - } - - for (int ii = 0; ii < metaObjects.count(); ++ii) { - metaObjects[ii].propertyOffset = - metaObjects.at(ii).metaObject->propertyOffset(); - metaObjects[ii].methodOffset = - metaObjects.at(ii).metaObject->methodOffset(); - } - - // Check for revisioned details - { - const QMetaObject *mo = nullptr; - if (metaObjects.isEmpty()) - mo = baseMetaObject; - else - mo = metaObjects.constFirst().metaObject; - - for (int ii = 0; !containsRevisionedAttributes && ii < mo->propertyCount(); ++ii) { - if (isPropertyRevisioned(mo, ii)) - containsRevisionedAttributes = true; - } - - for (int ii = 0; !containsRevisionedAttributes && ii < mo->methodCount(); ++ii) { - if (mo->method(ii).revision() != 0) - containsRevisionedAttributes = true; - } - } - - isSetup = true; - lock.unlock(); -} - -void QQmlTypePrivate::initEnums(const QQmlPropertyCache *cache) const -{ - if ((isEnumFromBaseSetup || !baseMetaObject) - && (isEnumFromCacheSetup || !cache)) { - return; - } - - init(); - - QMutexLocker lock(metaTypeDataLock()); - - if (!isEnumFromCacheSetup && cache) { - insertEnumsFromPropertyCache(cache); - isEnumFromCacheSetup = true; - } - - if (!isEnumFromBaseSetup && baseMetaObject) { // could be singleton type without metaobject - insertEnums(baseMetaObject); - isEnumFromBaseSetup = true; - } -} - -void QQmlTypePrivate::insertEnums(const QMetaObject *metaObject) const -{ - // Add any enum values defined by 'related' classes - if (metaObject->d.relatedMetaObjects) { - const auto *related = metaObject->d.relatedMetaObjects; - if (related) { - while (*related) - insertEnums(*related++); - } - } - - QSet<QString> localEnums; - const QMetaObject *localMetaObject = nullptr; - - // Add any enum values defined by this class, overwriting any inherited values - for (int ii = 0; ii < metaObject->enumeratorCount(); ++ii) { - QMetaEnum e = metaObject->enumerator(ii); - const bool isScoped = e.isScoped(); - QStringHash<int> *scoped = isScoped ? new QStringHash<int>() : nullptr; - - // We allow enums in sub-classes to overwrite enums from base-classes, such as - // ListView.Center (from enum PositionMode) overwriting Item.Center (from enum TransformOrigin). - // This is acceptable because the _use_ of the enum from the QML side requires qualification - // anyway, i.e. ListView.Center vs. Item.Center. - // However if a class defines two enums with the same value, then that must produce a warning - // because it represents a valid conflict. - if (e.enclosingMetaObject() != localMetaObject) { - localEnums.clear(); - localMetaObject = e.enclosingMetaObject(); - } - - for (int jj = 0; jj < e.keyCount(); ++jj) { - const QString key = QString::fromUtf8(e.key(jj)); - const int value = e.value(jj); - if (!isScoped || (regType == QQmlType::CppType && extraData.cd->registerEnumClassesUnscoped)) { - if (localEnums.contains(key)) { - auto existingEntry = enums.find(key); - if (existingEntry != enums.end() && existingEntry.value() != value) { - qWarning("Previously registered enum will be overwritten due to name clash: %s.%s", metaObject->className(), key.toUtf8().constData()); - createEnumConflictReport(metaObject, key); - } - } else { - localEnums.insert(key); - } - enums.insert(key, value); - } - if (isScoped) - scoped->insert(key, value); - } - - if (isScoped) { - scopedEnums << scoped; - scopedEnumIndex.insert(QString::fromUtf8(e.name()), scopedEnums.count()-1); - } - } -} - -void QQmlTypePrivate::createListOfPossibleConflictingItems(const QMetaObject *metaObject, QList<EnumInfo> &enumInfoList, QStringList path) const -{ - path.append(QString::fromUtf8(metaObject->className())); - - if (metaObject->d.relatedMetaObjects) { - const auto *related = metaObject->d.relatedMetaObjects; - if (related) { - while (*related) - createListOfPossibleConflictingItems(*related++, enumInfoList, path); - } - } - - for (int ii = 0; ii < metaObject->enumeratorCount(); ++ii) { - const auto e = metaObject->enumerator(ii); - - for (int jj = 0; jj < e.keyCount(); ++jj) { - const QString key = QString::fromUtf8(e.key(jj)); - - EnumInfo enumInfo; - enumInfo.metaObjectName = QString::fromUtf8(metaObject->className()); - enumInfo.enumName = QString::fromUtf8(e.name()); - enumInfo.enumKey = key; - enumInfo.scoped = e.isScoped(); - enumInfo.path = path; - enumInfo.metaEnumScope = QString::fromUtf8(e.scope()); - enumInfoList.append(enumInfo); - } - } -} - -void QQmlTypePrivate::createEnumConflictReport(const QMetaObject *metaObject, const QString &conflictingKey) const -{ - QList<EnumInfo> enumInfoList; - - if (baseMetaObject) // prefer baseMetaObject if available - metaObject = baseMetaObject; - - if (!metaObject) { // If there is no metaObject at all return early - qWarning() << "No meta object information available. Skipping conflict analysis."; - return; - } - - createListOfPossibleConflictingItems(metaObject, enumInfoList, QStringList()); - - qWarning().noquote() << QLatin1String("Possible conflicting items:"); - // find items with conflicting key - for (const auto i : enumInfoList) { - if (i.enumKey == conflictingKey) - qWarning().noquote().nospace() << " " << i.metaObjectName << "." << i.enumName << "." << i.enumKey << " from scope " - << i.metaEnumScope << " injected by " << i.path.join(QLatin1String("->")); - } -} - -void QQmlTypePrivate::insertEnumsFromPropertyCache(const QQmlPropertyCache *cache) const -{ - const QMetaObject *cppMetaObject = cache->firstCppMetaObject(); - - while (cache && cache->metaObject() != cppMetaObject) { - - int count = cache->qmlEnumCount(); - for (int ii = 0; ii < count; ++ii) { - QStringHash<int> *scoped = new QStringHash<int>(); - QQmlEnumData *enumData = cache->qmlEnum(ii); - - for (int jj = 0; jj < enumData->values.count(); ++jj) { - const QQmlEnumValue &value = enumData->values.at(jj); - enums.insert(value.namedValue, value.value); - scoped->insert(value.namedValue, value.value); - } - scopedEnums << scoped; - scopedEnumIndex.insert(enumData->name, scopedEnums.count()-1); - } - cache = cache->parent(); - } - insertEnums(cppMetaObject); -} - - -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) { - if (d->regType == SingletonType || d->regType == CompositeSingletonType) - return d->extraData.sd->singletonInstanceInfo->typeName.toUtf8(); - else if (d->baseMetaObject) - return d->baseMetaObject->className(); - } - return QByteArray(); -} - -QString QQmlType::elementName() const -{ - if (!d) - return QString(); - return d->elementName; -} - -QString QQmlType::qmlTypeName() const -{ - if (!d) - return QString(); - if (d->name.isEmpty()) { - if (!d->module.isEmpty()) - d->name = static_cast<QString>(d->module) + QLatin1Char('/') + d->elementName; - else - d->name = d->elementName; - } - - return d->name; -} - -QObject *QQmlType::create() const -{ - if (!d || !isCreatable()) - return nullptr; - - d->init(); - - QObject *rv = (QObject *)operator new(d->extraData.cd->allocationSize); - d->extraData.cd->newFunc(rv); - - if (rv && !d->metaObjects.isEmpty()) - (void)new QQmlProxyMetaObject(rv, &d->metaObjects); - - return rv; -} - -void QQmlType::create(QObject **out, void **memory, size_t additionalMemory) const -{ - if (!d || !isCreatable()) - return; - - d->init(); - - QObject *rv = (QObject *)operator new(d->extraData.cd->allocationSize + additionalMemory); - d->extraData.cd->newFunc(rv); - - if (rv && !d->metaObjects.isEmpty()) - (void)new QQmlProxyMetaObject(rv, &d->metaObjects); - - *out = rv; - *memory = ((char *)rv) + d->extraData.cd->allocationSize; -} - -QQmlType::SingletonInstanceInfo *QQmlType::singletonInstanceInfo() const -{ - if (!d) - return nullptr; - if (d->regType != SingletonType && d->regType != CompositeSingletonType) - return nullptr; - return d->extraData.sd->singletonInstanceInfo; -} - -QQmlCustomParser *QQmlType::customParser() const -{ - if (!d) - return nullptr; - if (d->regType != CppType) - return nullptr; - return d->extraData.cd->customParser; -} - -QQmlType::CreateFunc QQmlType::createFunction() const -{ - if (!d || d->regType != CppType) - return nullptr; - return d->extraData.cd->newFunc; -} - -QString QQmlType::noCreationReason() const -{ - if (!d || d->regType != CppType) - return QString(); - return d->extraData.cd->noCreationReason; -} - -bool QQmlType::isCreatable() const -{ - return d && d->regType == CppType && d->extraData.cd->newFunc; -} - -QQmlType::ExtensionFunc QQmlType::extensionFunction() const -{ - if (!d || d->regType != CppType) - return nullptr; - return d->extraData.cd->extFunc; -} - -bool QQmlType::isExtendedType() const -{ - if (!d) - return false; - d->init(); - - return !d->metaObjects.isEmpty(); -} - -bool QQmlType::isSingleton() const -{ - return d && (d->regType == SingletonType || d->regType == CompositeSingletonType); -} - -bool QQmlType::isInterface() const -{ - return d && d->regType == InterfaceType; -} - -bool QQmlType::isComposite() const -{ - return d && (d->regType == CompositeType || d->regType == CompositeSingletonType); -} - -bool QQmlType::isCompositeSingleton() const -{ - return d && d->regType == CompositeSingletonType; -} - -int QQmlType::typeId() const -{ - return d ? d->typeId : -1; -} - -int QQmlType::qListTypeId() const -{ - return d ? d->listId : -1; -} - -const QMetaObject *QQmlType::metaObject() const -{ - if (!d) - return nullptr; - d->init(); - - if (d->metaObjects.isEmpty()) - return d->baseMetaObject; - else - return d->metaObjects.constFirst().metaObject; - -} - -const QMetaObject *QQmlType::baseMetaObject() const -{ - return d ? d->baseMetaObject : nullptr; -} - -bool QQmlType::containsRevisionedAttributes() const -{ - if (!d) - return false; - d->init(); - - return d->containsRevisionedAttributes; -} - -int QQmlType::metaObjectRevision() const -{ - return d ? d->revision : -1; -} - -QQmlAttachedPropertiesFunc QQmlType::attachedPropertiesFunction(QQmlEnginePrivate *engine) const -{ - if (!d) - return nullptr; - if (d->regType == CppType) - return d->extraData.cd->attachedPropertiesFunc; - - QQmlType base; - if (d->regType == CompositeType) - base = resolveCompositeBaseType(engine); - return base.attachedPropertiesFunction(engine); -} - -const QMetaObject *QQmlType::attachedPropertiesType(QQmlEnginePrivate *engine) const -{ - if (!d) - return nullptr; - if (d->regType == CppType) - return d->extraData.cd->attachedPropertiesType; - - QQmlType base; - if (d->regType == CompositeType) - base = resolveCompositeBaseType(engine); - return base.attachedPropertiesType(engine); -} - -/* -This is the id passed to qmlAttachedPropertiesById(). This is different from the index -for the case that a single class is registered under two or more names (eg. Item in -Qt 4.7 and QtQuick 1.0). -*/ -int QQmlType::attachedPropertiesId(QQmlEnginePrivate *engine) const -{ - if (!d) - return -1; - if (d->regType == CppType) - return d->extraData.cd->attachedPropertiesId; - - QQmlType base; - if (d->regType == CompositeType) - base = resolveCompositeBaseType(engine); - return base.attachedPropertiesId(engine); -} - -int QQmlType::parserStatusCast() const -{ - if (!d || d->regType != CppType) - return -1; - return d->extraData.cd->parserStatusCast; -} - -int QQmlType::propertyValueSourceCast() const -{ - if (!d || d->regType != CppType) - return -1; - return d->extraData.cd->propertyValueSourceCast; -} - -int QQmlType::propertyValueInterceptorCast() const -{ - if (!d || d->regType != CppType) - return -1; - return d->extraData.cd->propertyValueInterceptorCast; -} - -const char *QQmlType::interfaceIId() const -{ - if (!d || d->regType != InterfaceType) - return nullptr; - return d->iid; -} - -int QQmlType::index() const -{ - return d ? d->index : -1; -} - -QUrl QQmlType::sourceUrl() const -{ - if (d) { - if (d->regType == CompositeType) - return d->extraData.fd->url; - else if (d->regType == CompositeSingletonType) - return d->extraData.sd->singletonInstanceInfo->url; - } - return QUrl(); -} - -int QQmlType::enumValue(QQmlEnginePrivate *engine, const QHashedStringRef &name, bool *ok) const -{ - Q_ASSERT(ok); - if (d) { - const QQmlPropertyCache *cache = isComposite() ? compositePropertyCache(engine) : nullptr; - - *ok = true; - - d->initEnums(cache); - - int *rv = d->enums.value(name); - if (rv) - return *rv; - } - - *ok = false; - return -1; -} - -int QQmlType::enumValue(QQmlEnginePrivate *engine, const QHashedCStringRef &name, bool *ok) const -{ - Q_ASSERT(ok); - if (d) { - const QQmlPropertyCache *cache = isComposite() ? compositePropertyCache(engine) : nullptr; - - *ok = true; - - d->initEnums(cache); - - int *rv = d->enums.value(name); - if (rv) - return *rv; - } - - *ok = false; - return -1; -} - -int QQmlType::enumValue(QQmlEnginePrivate *engine, const QV4::String *name, bool *ok) const -{ - Q_ASSERT(ok); - if (d) { - const QQmlPropertyCache *cache = isComposite() ? compositePropertyCache(engine) : nullptr; - *ok = true; - - d->initEnums(cache); - - int *rv = d->enums.value(name); - if (rv) - return *rv; - } - - *ok = false; - return -1; -} - -int QQmlType::scopedEnumIndex(QQmlEnginePrivate *engine, const QV4::String *name, bool *ok) const -{ - Q_ASSERT(ok); - if (d) { - const QQmlPropertyCache *cache = isComposite() ? compositePropertyCache(engine) : nullptr; - *ok = true; - - d->initEnums(cache); - - int *rv = d->scopedEnumIndex.value(name); - if (rv) - return *rv; - } - - *ok = false; - return -1; -} - -int QQmlType::scopedEnumIndex(QQmlEnginePrivate *engine, const QString &name, bool *ok) const -{ - Q_ASSERT(ok); - if (d) { - const QQmlPropertyCache *cache = isComposite() ? compositePropertyCache(engine) : nullptr; - *ok = true; - - d->initEnums(cache); - - int *rv = d->scopedEnumIndex.value(name); - if (rv) - return *rv; - } - - *ok = false; - return -1; -} - -int QQmlType::scopedEnumValue(QQmlEnginePrivate *engine, int index, const QV4::String *name, bool *ok) const -{ - Q_UNUSED(engine) - Q_ASSERT(ok); - *ok = true; - - if (d) { - Q_ASSERT(index > -1 && index < d->scopedEnums.count()); - int *rv = d->scopedEnums.at(index)->value(name); - if (rv) - return *rv; - } - - *ok = false; - return -1; -} - -int QQmlType::scopedEnumValue(QQmlEnginePrivate *engine, int index, const QString &name, bool *ok) const -{ - Q_UNUSED(engine) - Q_ASSERT(ok); - *ok = true; - - if (d) { - Q_ASSERT(index > -1 && index < d->scopedEnums.count()); - int *rv = d->scopedEnums.at(index)->value(name); - if (rv) - return *rv; - } - - *ok = false; - return -1; -} - -int QQmlType::scopedEnumValue(QQmlEnginePrivate *engine, const QByteArray &scopedEnumName, const QByteArray &name, bool *ok) const -{ - Q_ASSERT(ok); - if (d) { - const QQmlPropertyCache *cache = isComposite() ? compositePropertyCache(engine) : nullptr; - *ok = true; - - d->initEnums(cache); - - int *rv = d->scopedEnumIndex.value(QHashedCStringRef(scopedEnumName.constData(), scopedEnumName.length())); - if (rv) { - int index = *rv; - Q_ASSERT(index > -1 && index < d->scopedEnums.count()); - rv = d->scopedEnums.at(index)->value(QHashedCStringRef(name.constData(), name.length())); - if (rv) - return *rv; - } - } - - *ok = false; - return -1; -} - -int QQmlType::scopedEnumValue(QQmlEnginePrivate *engine, const QStringRef &scopedEnumName, const QStringRef &name, bool *ok) const -{ - Q_ASSERT(ok); - if (d) { - const QQmlPropertyCache *cache = isComposite() ? compositePropertyCache(engine) : nullptr; - *ok = true; - - d->initEnums(cache); - - int *rv = d->scopedEnumIndex.value(QHashedStringRef(scopedEnumName)); - if (rv) { - int index = *rv; - Q_ASSERT(index > -1 && index < d->scopedEnums.count()); - rv = d->scopedEnums.at(index)->value(QHashedStringRef(name)); - if (rv) - return *rv; - } - } - - *ok = false; - return -1; -} - -void QQmlType::refHandle(QQmlTypePrivate *priv) -{ - if (priv) - priv->refCount.ref(); -} - -void QQmlType::derefHandle(QQmlTypePrivate *priv) -{ - if (priv && !priv->refCount.deref()) - delete priv; -} - -int QQmlType::refCount(QQmlTypePrivate *priv) -{ - if (priv) - return priv->refCount; - return -1; -} - -namespace { -template <typename QQmlTypeContainer> -void removeQQmlTypePrivate(QQmlTypeContainer &container, const QQmlTypePrivate *reference) -{ - for (typename QQmlTypeContainer::iterator it = container.begin(); it != container.end();) { - if (*it == reference) - it = container.erase(it); - else - ++it; - } -} - -struct IsQQmlTypePrivate -{ - const QQmlTypePrivate *reference; - explicit IsQQmlTypePrivate(const QQmlTypePrivate *ref) : reference(ref) {} - - bool operator()(const QQmlTypePrivate *priv) const { return reference == priv; } -}; -} - -QQmlTypeModule::QQmlTypeModule() -: d(new QQmlTypeModulePrivate) -{ -} - -QQmlTypeModule::~QQmlTypeModule() -{ - delete d; d = nullptr; -} - -QString QQmlTypeModule::module() const -{ - return d->uri.uri; -} - -int QQmlTypeModule::majorVersion() const -{ - return d->uri.majorVersion; -} - -int QQmlTypeModule::minimumMinorVersion() const -{ - return d->minMinorVersion; -} - -int QQmlTypeModule::maximumMinorVersion() const -{ - return d->maxMinorVersion; -} - -void QQmlTypeModulePrivate::add(QQmlTypePrivate *type) -{ - int minVersion = type->version_min; - minMinorVersion = qMin(minMinorVersion, minVersion); - maxMinorVersion = qMax(maxMinorVersion, minVersion); - - QList<QQmlTypePrivate *> &list = typeHash[type->elementName]; - for (int ii = 0; ii < list.count(); ++ii) { - Q_ASSERT(list.at(ii)); - if (list.at(ii)->version_min < minVersion) { - list.insert(ii, type); - return; - } - } - list.append(type); -} - -void QQmlTypeModulePrivate::remove(const QQmlTypePrivate *type) -{ - for (TypeHash::ConstIterator elementIt = typeHash.begin(); elementIt != typeHash.end();) { - QList<QQmlTypePrivate *> &list = const_cast<QList<QQmlTypePrivate *> &>(elementIt.value()); - - removeQQmlTypePrivate(list, type); - -#if 0 - if (list.isEmpty()) - elementIt = typeHash.erase(elementIt); - else - ++elementIt; -#else - ++elementIt; -#endif - } -} - -QQmlType QQmlTypeModule::type(const QHashedStringRef &name, int minor) const -{ - QMutexLocker lock(metaTypeDataLock()); - - QList<QQmlTypePrivate *> *types = d->typeHash.value(name); - if (types) { - for (int ii = 0; ii < types->count(); ++ii) - if (types->at(ii)->version_min <= minor) - return QQmlType(types->at(ii)); - } - - return QQmlType(); -} - -QQmlType QQmlTypeModule::type(const QV4::String *name, int minor) const -{ - QMutexLocker lock(metaTypeDataLock()); - - QList<QQmlTypePrivate *> *types = d->typeHash.value(name); - if (types) { - for (int ii = 0; ii < types->count(); ++ii) - if (types->at(ii)->version_min <= minor) - return QQmlType(types->at(ii)); - } - - return QQmlType(); -} - -void QQmlTypeModule::walkCompositeSingletons(const std::function<void(const QQmlType &)> &callback) const -{ - QMutexLocker lock(metaTypeDataLock()); - for (auto typeCandidates = d->typeHash.begin(), end = d->typeHash.end(); - typeCandidates != end; ++typeCandidates) { - for (auto type: typeCandidates.value()) { - if (type->regType == QQmlType::CompositeSingletonType) - callback(QQmlType(type)); - } - } -} - -QQmlTypeModuleVersion::QQmlTypeModuleVersion() -: m_module(nullptr), m_minor(0) -{ -} - -QQmlTypeModuleVersion::QQmlTypeModuleVersion(QQmlTypeModule *module, int minor) -: m_module(module), m_minor(minor) -{ - Q_ASSERT(m_module); - Q_ASSERT(m_minor >= 0); -} - -QQmlTypeModuleVersion::QQmlTypeModuleVersion(const QQmlTypeModuleVersion &o) -: m_module(o.m_module), m_minor(o.m_minor) -{ -} - -QQmlTypeModuleVersion &QQmlTypeModuleVersion::operator=(const QQmlTypeModuleVersion &o) -{ - m_module = o.m_module; - m_minor = o.m_minor; - return *this; -} - -QQmlTypeModule *QQmlTypeModuleVersion::module() const -{ - return m_module; -} - -int QQmlTypeModuleVersion::minorVersion() const -{ - return m_minor; -} - -QQmlType QQmlTypeModuleVersion::type(const QHashedStringRef &name) const -{ - if (!m_module) - return QQmlType(); - return m_module->type(name, m_minor); -} - -QQmlType QQmlTypeModuleVersion::type(const QV4::String *name) const -{ - if (!m_module) - return QQmlType(); - return m_module->type(name, m_minor); -} - -void qmlClearTypeRegistrations() // Declared in qqml.h +void QQmlMetaType::clearTypeRegistrations() { //Only cleans global static, assumed no running engine - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + QQmlMetaTypeDataPtr data; for (QQmlMetaTypeData::TypeModules::const_iterator i = data->uriToModule.constBegin(), cend = data->uriToModule.constEnd(); i != cend; ++i) delete *i; @@ -1655,33 +282,24 @@ void qmlClearTypeRegistrations() // Declared in qqml.h data->metaObjectToType.clear(); data->uriToModule.clear(); data->undeletableTypes.clear(); - - QQmlEnginePrivate::baseModulesUninitialized = true; //So the engine re-registers its types -#if QT_CONFIG(library) - qmlClearEnginePlugins(); -#endif } -static int registerAutoParentFunction(QQmlPrivate::RegisterAutoParent &autoparent) +int QQmlMetaType::registerAutoParentFunction(QQmlPrivate::RegisterAutoParent &autoparent) { - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + QQmlMetaTypeDataPtr data; data->parentFunctions.append(autoparent.function); return data->parentFunctions.count() - 1; } -QQmlType registerInterface(const QQmlPrivate::RegisterInterface &interface) +QQmlType QQmlMetaType::registerInterface(const QQmlPrivate::RegisterInterface &type) { - if (interface.version > 0) + if (type.version > 0) qFatal("qmlRegisterType(): Cannot mix incompatible QML versions."); - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); - - QQmlType type(data, interface); - QQmlTypePrivate *priv = type.priv(); + QQmlMetaTypeDataPtr data; + QQmlTypePrivate *priv = createQQmlType(data, type); Q_ASSERT(priv); data->idToType.insert(priv->typeId, priv); @@ -1690,14 +308,14 @@ QQmlType registerInterface(const QQmlPrivate::RegisterInterface &interface) if (!priv->elementName.isEmpty()) data->nameToType.insert(priv->elementName, priv); - if (data->interfaces.size() <= interface.typeId) - data->interfaces.resize(interface.typeId + 16); - if (data->lists.size() <= interface.listId) - data->lists.resize(interface.listId + 16); - data->interfaces.setBit(interface.typeId, true); - data->lists.setBit(interface.listId, true); + if (data->interfaces.size() <= type.typeId) + data->interfaces.resize(type.typeId + 16); + if (data->lists.size() <= type.listId) + data->lists.resize(type.listId + 16); + data->interfaces.setBit(type.typeId, true); + data->lists.setBit(type.listId, true); - return type; + return QQmlType(priv); } QString registrationTypeString(QQmlType::RegistrationType typeType) @@ -1715,7 +333,8 @@ QString registrationTypeString(QQmlType::RegistrationType typeType) } // NOTE: caller must hold a QMutexLocker on "data" -bool checkRegistration(QQmlType::RegistrationType typeType, QQmlMetaTypeData *data, const char *uri, const QString &typeName, int majorVersion = -1) +bool checkRegistration(QQmlType::RegistrationType typeType, QQmlMetaTypeData *data, + const char *uri, const QString &typeName, int majorVersion = -1) { if (!typeName.isEmpty()) { if (typeName.at(0).isLower()) { @@ -1750,7 +369,7 @@ bool checkRegistration(QQmlType::RegistrationType typeType, QQmlMetaTypeData *da versionedUri.uri = nameSpace; versionedUri.majorVersion = majorVersion; if (QQmlTypeModule* qqtm = data->uriToModule.value(versionedUri, 0)){ - if (QQmlTypeModulePrivate::get(qqtm)->locked){ + if (qqtm->isLocked()){ QString failure(QCoreApplication::translate("qmlRegisterType", "Cannot install %1 '%2' into protected module '%3' version '%4'")); data->recordTypeRegFailure(failure.arg(registrationTypeString(typeType)).arg(typeName).arg(nameSpace).arg(majorVersion)); @@ -1769,8 +388,7 @@ QQmlTypeModule *getTypeModule(const QHashedString &uri, int majorVersion, QQmlMe QQmlMetaTypeData::VersionedUri versionedUri(uri, majorVersion); QQmlTypeModule *module = data->uriToModule.value(versionedUri); if (!module) { - module = new QQmlTypeModule; - module->d->uri = versionedUri; + module = new QQmlTypeModule(versionedUri.uri, versionedUri.majorVersion); data->uriToModule.insert(versionedUri, module); } return module; @@ -1806,47 +424,47 @@ void addTypeToData(QQmlTypePrivate *type, QQmlMetaTypeData *data) QQmlTypeModule *module = getTypeModule(mod, type->version_maj, data); Q_ASSERT(module); - module->d->add(type); + module->add(type); } } -QQmlType registerType(const QQmlPrivate::RegisterType &type) +QQmlType QQmlMetaType::registerType(const QQmlPrivate::RegisterType &type) { - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + QQmlMetaTypeDataPtr data; + QString elementName = QString::fromUtf8(type.elementName); if (!checkRegistration(QQmlType::CppType, data, type.uri, elementName, type.versionMajor)) return QQmlType(); - QQmlType dtype(data, elementName, type); + QQmlTypePrivate *priv = createQQmlType(data, elementName, type); - addTypeToData(dtype.priv(), data); + addTypeToData(priv, data); if (!type.typeId) - data->idToType.insert(dtype.typeId(), dtype.priv()); + data->idToType.insert(priv->typeId, priv); - return dtype; + return QQmlType(priv); } -QQmlType registerSingletonType(const QQmlPrivate::RegisterSingletonType &type) +QQmlType QQmlMetaType::registerSingletonType(const QQmlPrivate::RegisterSingletonType &type) { - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + QQmlMetaTypeDataPtr data; + QString typeName = QString::fromUtf8(type.typeName); if (!checkRegistration(QQmlType::SingletonType, data, type.uri, typeName, type.versionMajor)) return QQmlType(); - QQmlType dtype(data, typeName, type); + QQmlTypePrivate *priv = createQQmlType(data, typeName, type); - addTypeToData(dtype.priv(), data); + addTypeToData(priv, data); - return dtype; + return QQmlType(priv); } QQmlType QQmlMetaType::registerCompositeSingletonType(const QQmlPrivate::RegisterCompositeSingletonType &type) { // Assumes URL is absolute and valid. Checking of user input should happen before the URL enters type. - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + QQmlMetaTypeDataPtr data; + QString typeName = QString::fromUtf8(type.typeName); bool fileImport = false; if (*(type.uri) == '\0') @@ -1854,21 +472,20 @@ QQmlType QQmlMetaType::registerCompositeSingletonType(const QQmlPrivate::Registe if (!checkRegistration(QQmlType::CompositeSingletonType, data, fileImport ? nullptr : type.uri, typeName)) return QQmlType(); - QQmlType dtype(data, typeName, type); - - addTypeToData(dtype.priv(), data); + QQmlTypePrivate *priv = createQQmlType(data, typeName, type); + addTypeToData(priv, data); QQmlMetaTypeData::Files *files = fileImport ? &(data->urlToType) : &(data->urlToNonFileImportType); - files->insertMulti(QQmlTypeLoader::normalize(type.url), dtype.priv()); + files->insertMulti(QQmlTypeLoader::normalize(type.url), priv); - return dtype; + return QQmlType(priv); } QQmlType QQmlMetaType::registerCompositeType(const QQmlPrivate::RegisterCompositeType &type) { // Assumes URL is absolute and valid. Checking of user input should happen before the URL enters type. - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + QQmlMetaTypeDataPtr data; + QString typeName = QString::fromUtf8(type.typeName); bool fileImport = false; if (*(type.uri) == '\0') @@ -1876,13 +493,13 @@ QQmlType QQmlMetaType::registerCompositeType(const QQmlPrivate::RegisterComposit if (!checkRegistration(QQmlType::CompositeType, data, fileImport?nullptr:type.uri, typeName, type.versionMajor)) return QQmlType(); - QQmlType dtype(data, typeName, type); - addTypeToData(dtype.priv(), data); + QQmlTypePrivate *priv = createQQmlType(data, typeName, type); + addTypeToData(priv, data); QQmlMetaTypeData::Files *files = fileImport ? &(data->urlToType) : &(data->urlToNonFileImportType); - files->insertMulti(QQmlTypeLoader::normalize(type.url), dtype.priv()); + files->insertMulti(QQmlTypeLoader::normalize(type.url), priv); - return dtype; + return QQmlType(priv); } void QQmlMetaType::registerInternalCompositeType(QV4::CompiledData::CompilationUnit *compilationUnit) @@ -1908,9 +525,8 @@ void QQmlMetaType::registerInternalCompositeType(QV4::CompiledData::CompilationU compilationUnit->metaTypeId = ptr_type; compilationUnit->listMetaTypeId = lst_type; - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *d = metaTypeData(); - d->qmlLists.insert(lst_type, ptr_type); + QQmlMetaTypeDataPtr data; + data->qmlLists.insert(lst_type, ptr_type); } void QQmlMetaType::unregisterInternalCompositeType(QV4::CompiledData::CompilationUnit *compilationUnit) @@ -1918,95 +534,52 @@ void QQmlMetaType::unregisterInternalCompositeType(QV4::CompiledData::Compilatio int ptr_type = compilationUnit->metaTypeId; int lst_type = compilationUnit->listMetaTypeId; - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *d = metaTypeData(); - d->qmlLists.remove(lst_type); + QQmlMetaTypeDataPtr data; + data->qmlLists.remove(lst_type); QMetaType::unregisterType(ptr_type); QMetaType::unregisterType(lst_type); } -int registerQmlUnitCacheHook(const QQmlPrivate::RegisterQmlUnitCacheHook &hookRegistration) +int QQmlMetaType::registerUnitCacheHook( + const QQmlPrivate::RegisterQmlUnitCacheHook &hookRegistration) { if (hookRegistration.version > 0) qFatal("qmlRegisterType(): Cannot mix incompatible QML versions."); - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + + QQmlMetaTypeDataPtr data; data->lookupCachedQmlUnit << hookRegistration.lookupCachedQmlUnit; return 0; } -/* -This method is "over generalized" to allow us to (potentially) register more types of things in -the future without adding exported symbols. -*/ -int QQmlPrivate::qmlregister(RegistrationType type, void *data) -{ - if (type == AutoParentRegistration) - return registerAutoParentFunction(*reinterpret_cast<RegisterAutoParent *>(data)); - else if (type == QmlUnitCacheHookRegistration) - return registerQmlUnitCacheHook(*reinterpret_cast<RegisterQmlUnitCacheHook *>(data)); - - QQmlType dtype; - if (type == TypeRegistration) - dtype = registerType(*reinterpret_cast<RegisterType *>(data)); - else if (type == InterfaceRegistration) - dtype = registerInterface(*reinterpret_cast<RegisterInterface *>(data)); - else if (type == SingletonRegistration) - dtype = registerSingletonType(*reinterpret_cast<RegisterSingletonType *>(data)); - else if (type == CompositeRegistration) - dtype = QQmlMetaType::registerCompositeType(*reinterpret_cast<RegisterCompositeType *>(data)); - else if (type == CompositeSingletonRegistration) - dtype = QQmlMetaType::registerCompositeSingletonType(*reinterpret_cast<RegisterCompositeSingletonType *>(data)); - else - return -1; - - if (!dtype.isValid()) - return -1; - - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *typeData = metaTypeData(); - typeData->undeletableTypes.insert(dtype); - - return dtype.index(); -} - -//From qqml.h -bool qmlProtectModule(const char *uri, int majVersion) +bool QQmlMetaType::protectModule(const char *uri, int majVersion) { - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + QQmlMetaTypeDataPtr data; QQmlMetaTypeData::VersionedUri versionedUri; versionedUri.uri = QString::fromUtf8(uri); versionedUri.majorVersion = majVersion; if (QQmlTypeModule* qqtm = data->uriToModule.value(versionedUri, 0)) { - QQmlTypeModulePrivate::get(qqtm)->locked = true; + qqtm->lock(); return true; } return false; } -//From qqml.h -void qmlRegisterModule(const char *uri, int versionMajor, int versionMinor) +void QQmlMetaType::registerModule(const char *uri, int versionMajor, int versionMinor) { - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + QQmlMetaTypeDataPtr data; QQmlTypeModule *module = getTypeModule(QString::fromUtf8(uri), versionMajor, data); Q_ASSERT(module); - QQmlTypeModulePrivate *p = QQmlTypeModulePrivate::get(module); - p->minMinorVersion = qMin(p->minMinorVersion, versionMinor); - p->maxMinorVersion = qMax(p->maxMinorVersion, versionMinor); + module->addMinorVersion(versionMinor); } -//From qqml.h -int qmlTypeId(const char *uri, int versionMajor, int versionMinor, const char *qmlName) +int QQmlMetaType::typeId(const char *uri, int versionMajor, int versionMinor, const char *qmlName) { - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + QQmlMetaTypeDataPtr data; QQmlTypeModule *module = getTypeModule(QString::fromUtf8(uri), versionMajor, data); if (!module) @@ -2019,31 +592,221 @@ int qmlTypeId(const char *uri, int versionMajor, int versionMinor, const char *q return type.index(); } -bool QQmlMetaType::namespaceContainsRegistrations(const QString &uri, int majorVersion) +void QQmlMetaType::registerUndeletableType(const QQmlType &dtype) { - const QQmlMetaTypeData *data = metaTypeData(); + QQmlMetaTypeDataPtr data; + data->undeletableTypes.insert(dtype); +} + +int QQmlMetaType::registerAttachedPropertyId(const QMetaObject *metaObject, int index) +{ + QQmlMetaTypeDataPtr data; + return data->attachedPropertyId(metaObject, index); +} +bool QQmlMetaType::unregisterAttachedPropertyId(const QMetaObject *metaObject, int index) +{ + QQmlMetaTypeDataPtr data; + // This is run from the QQmlType dtor. QQmlTypes in user code can outlive QQmlMetaTypeData. + return data ? data->removeAttachedPropertyId(metaObject, index) : false; +} + +static bool namespaceContainsRegistrations(const QQmlMetaTypeData *data, const QString &uri, + int majorVersion) +{ // Has any type previously been installed to this namespace? QHashedString nameSpace(uri); - for (const QQmlType &type : data->types) + for (const QQmlType &type : data->types) { if (type.module() == nameSpace && type.majorVersion() == majorVersion) return true; + } return false; } -void QQmlMetaType::protectNamespace(const QString &uri) +class QQmlMetaTypeRegistrationFailureRecorder +{ + Q_DISABLE_COPY_MOVE(QQmlMetaTypeRegistrationFailureRecorder) +public: + QQmlMetaTypeRegistrationFailureRecorder(QQmlMetaTypeData *data, QStringList *failures) + : data(data) + { + data->setTypeRegistrationFailures(failures); + } + + ~QQmlMetaTypeRegistrationFailureRecorder() + { + data->setTypeRegistrationFailures(nullptr); + } + + QQmlMetaTypeData *data = nullptr; +}; + + +bool QQmlMetaType::registerPluginTypes(QObject *instance, const QString &basePath, + const QString &uri, const QString &typeNamespace, int vmaj, + QList<QQmlError> *errors) { - QQmlMetaTypeData *data = metaTypeData(); + QQmlTypesExtensionInterface *iface = qobject_cast<QQmlTypesExtensionInterface *>(instance); + if (!iface) { + if (errors) { + QQmlError error; + error.setDescription(QStringLiteral("Module loaded for URI '%1' does not implement " + "QQmlTypesExtensionInterface").arg(typeNamespace)); + errors->prepend(error); + } + return false; + } + + if (!typeNamespace.isEmpty() && typeNamespace != uri) { + // This is an 'identified' module + // The namespace for type registrations must match the URI for locating the module + if (errors) { + QQmlError error; + error.setDescription( + QStringLiteral("Module namespace '%1' does not match import URI '%2'") + .arg(typeNamespace).arg(uri)); + errors->prepend(error); + } + return false; + } + + QStringList failures; + QQmlMetaTypeDataPtr data; + { + QQmlMetaTypeRegistrationFailureRecorder failureRecorder(data, &failures); + if (!typeNamespace.isEmpty()) { + // This is an 'identified' module + if (namespaceContainsRegistrations(data, typeNamespace, vmaj)) { + // Other modules have already installed to this namespace + if (errors) { + QQmlError error; + error.setDescription(QStringLiteral("Namespace '%1' has already been used " + "for type registration") + .arg(typeNamespace)); + errors->prepend(error); + } + return false; + } + + data->protectedNamespaces.insert(uri); + } else { + // This is not an identified module - provide a warning + qWarning().nospace() << qPrintable( + QStringLiteral("Module '%1' does not contain a module identifier directive - " + "it cannot be protected from external registrations.").arg(uri)); + } + + if (auto *plugin = qobject_cast<QQmlExtensionPlugin *>(instance)) { + // basepath should point to the directory of the module, not the plugin file itself: + QQmlExtensionPluginPrivate::get(plugin)->baseUrl + = QQmlImports::urlFromLocalFileOrQrcOrUrl(basePath); + } + + data->typeRegistrationNamespace = typeNamespace; + const QByteArray bytes = uri.toUtf8(); + const char *moduleId = bytes.constData(); + iface->registerTypes(moduleId); + data->typeRegistrationNamespace.clear(); + } + + if (!failures.isEmpty()) { + if (errors) { + for (const QString &failure : qAsConst(failures)) { + QQmlError error; + error.setDescription(failure); + errors->prepend(error); + } + } + return false; + } - data->protectedNamespaces.insert(uri); + return true; } -void QQmlMetaType::setTypeRegistrationNamespace(const QString &uri) -{ - QQmlMetaTypeData *data = metaTypeData(); +/* + \internal + + Fetches the QQmlType instance registered for \a urlString, creating a + registration for it if it is not already registered, using the associated + \a typeName, \a isCompositeSingleton, \a majorVersion and \a minorVersion + details. + + Errors (if there are any) are placed into \a errors, if it is nonzero. + Otherwise errors are printed as warnings. +*/ +QQmlType QQmlMetaType::typeForUrl(const QString &urlString, + const QHashedStringRef &qualifiedType, + bool isCompositeSingleton, QList<QQmlError> *errors, + int majorVersion, int minorVersion) +{ + // ### unfortunate (costly) conversion + const QUrl url = QQmlTypeLoader::normalize(QUrl(urlString)); + + QQmlMetaTypeDataPtr data; + QQmlType ret(data->urlToType.value(url)); + if (ret.isValid() && ret.sourceUrl() == url) + return ret; + + const int dot = qualifiedType.indexOf(QLatin1Char('.')); + const QString typeName = dot < 0 + ? qualifiedType.toString() + : QString(qualifiedType.constData() + dot + 1, qualifiedType.length() - dot - 1); + + QStringList failures; + QQmlMetaTypeRegistrationFailureRecorder failureRecorder(data, &failures); + + // Register the type. Note that the URI parameters here are empty; for + // file type imports, we do not place them in a URI as we don't + // necessarily have a good and unique one (picture a library import, + // which may be found in multiple plugin locations on disk), but there + // are other reasons for this too. + // + // By not putting them in a URI, we prevent the types from being + // registered on a QQmlTypeModule; this is important, as once types are + // placed on there, they cannot be easily removed, meaning if the + // developer subsequently loads a different import (meaning different + // types) with the same URI (using, say, a different plugin path), it is + // very undesirable that we continue to associate the types from the + // "old" URI with that new module. + // + // Not having URIs also means that the types cannot be found by name + // etc, the only way to look them up is through QQmlImports -- for + // better or worse. + const QQmlType::RegistrationType registrationType = isCompositeSingleton + ? QQmlType::CompositeSingletonType + : QQmlType::CompositeType; + if (checkRegistration(registrationType, data, nullptr, typeName, majorVersion)) { + auto *priv = new QQmlTypePrivate(registrationType); + priv->setName(QString(), typeName); + priv->version_maj = majorVersion; + priv->version_min = minorVersion; + + if (isCompositeSingleton) { + priv->extraData.sd->singletonInstanceInfo = new QQmlType::SingletonInstanceInfo; + priv->extraData.sd->singletonInstanceInfo->url = url; + priv->extraData.sd->singletonInstanceInfo->typeName = typeName; + } else { + priv->extraData.fd->url = url; + } - data->typeRegistrationNamespace = uri; + data->registerType(priv); + addTypeToData(priv, data); + data->urlToType.insertMulti(url, priv); + return QQmlType(priv); + } + + // This means that the type couldn't be found by URL, but could not be + // registered either, meaning we most likely were passed some kind of bad + // data. + if (errors) { + QQmlError error; + error.setDescription(failures.join('\n')); + errors->prepend(error); + } else { + qWarning("%s", failures.join('\n').toLatin1().constData()); + } + return QQmlType(); } QMutex *QQmlMetaType::typeRegistrationLock() @@ -2056,8 +819,7 @@ QMutex *QQmlMetaType::typeRegistrationLock() */ bool QQmlMetaType::isAnyModule(const QString &uri) { - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + QQmlMetaTypeDataPtr data; for (QQmlMetaTypeData::TypeModules::ConstIterator iter = data->uriToModule.cbegin(); iter != data->uriToModule.cend(); ++iter) { @@ -2073,14 +835,13 @@ bool QQmlMetaType::isAnyModule(const QString &uri) */ bool QQmlMetaType::isLockedModule(const QString &uri, int majVersion) { - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + QQmlMetaTypeDataPtr data; QQmlMetaTypeData::VersionedUri versionedUri; versionedUri.uri = uri; versionedUri.majorVersion = majVersion; if (QQmlTypeModule* qqtm = data->uriToModule.value(versionedUri, 0)) - return QQmlTypeModulePrivate::get(qqtm)->locked; + return qqtm->isLocked(); return false; } @@ -2094,9 +855,7 @@ bool QQmlMetaType::isLockedModule(const QString &uri, int majVersion) bool QQmlMetaType::isModule(const QString &module, int versionMajor, int versionMinor) { Q_ASSERT(versionMajor >= 0 && versionMinor >= 0); - QMutexLocker lock(metaTypeDataLock()); - - QQmlMetaTypeData *data = metaTypeData(); + QQmlMetaTypeDataPtr data; // first, check Types QQmlTypeModule *tm = @@ -2109,15 +868,13 @@ bool QQmlMetaType::isModule(const QString &module, int versionMajor, int version QQmlTypeModule *QQmlMetaType::typeModule(const QString &uri, int majorVersion) { - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + QQmlMetaTypeDataPtr data; return data->uriToModule.value(QQmlMetaTypeData::VersionedUri(uri, majorVersion)); } QList<QQmlPrivate::AutoParentFunction> QQmlMetaType::parentFunctions() { - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + QQmlMetaTypeDataPtr data; return data->parentFunctions; } @@ -2138,8 +895,7 @@ bool QQmlMetaType::isQObject(int userType) if (userType == QMetaType::QObjectStar) return true; - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + QQmlMetaTypeDataPtr data; return userType >= 0 && userType < data->objects.size() && data->objects.testBit(userType); } @@ -2148,8 +904,7 @@ bool QQmlMetaType::isQObject(int userType) */ int QQmlMetaType::listType(int id) { - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + QQmlMetaTypeDataPtr data; QHash<int, int>::ConstIterator iter = data->qmlLists.constFind(id); if (iter != data->qmlLists.cend()) return *iter; @@ -2162,8 +917,7 @@ int QQmlMetaType::listType(int id) int QQmlMetaType::attachedPropertiesFuncId(QQmlEnginePrivate *engine, const QMetaObject *mo) { - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + QQmlMetaTypeDataPtr data; for (auto it = data->metaObjectToType.constFind(mo), end = data->metaObjectToType.constEnd(); it != end && it.key() == mo; ++it) { @@ -2179,8 +933,7 @@ QQmlAttachedPropertiesFunc QQmlMetaType::attachedPropertiesFuncById(QQmlEnginePr { if (id < 0) return nullptr; - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + QQmlMetaTypeDataPtr data; return data->types.at(id).attachedPropertiesFunction(engine); } @@ -2243,8 +996,7 @@ QQmlMetaType::TypeCategory QQmlMetaType::typeCategory(int userType) if (userType == QMetaType::QObjectStar) return Object; - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + QQmlMetaTypeDataPtr data; if (data->qmlLists.contains(userType)) return List; else if (userType < data->objects.size() && data->objects.testBit(userType)) @@ -2260,17 +1012,20 @@ QQmlMetaType::TypeCategory QQmlMetaType::typeCategory(int userType) */ bool QQmlMetaType::isInterface(int userType) { - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + const QQmlMetaTypeDataPtr data; return userType >= 0 && userType < data->interfaces.size() && data->interfaces.testBit(userType); } const char *QQmlMetaType::interfaceIId(int userType) { - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); - QQmlType type(data->idToType.value(userType)); - lock.unlock(); + + QQmlTypePrivate *typePrivate = nullptr; + { + QQmlMetaTypeDataPtr data; + typePrivate = data->idToType.value(userType); + } + + QQmlType type(typePrivate); if (type.isInterface() && type.typeId() == userType) return type.interfaceIId(); else @@ -2279,8 +1034,7 @@ const char *QQmlMetaType::interfaceIId(int userType) bool QQmlMetaType::isList(int userType) { - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + const QQmlMetaTypeDataPtr data; if (data->qmlLists.contains(userType)) return true; return userType >= 0 && userType < data->lists.size() && data->lists.testBit(userType); @@ -2303,9 +1057,7 @@ bool QQmlMetaType::isList(int userType) */ void QQmlMetaType::registerCustomStringConverter(int type, StringConverter converter) { - QMutexLocker lock(metaTypeDataLock()); - - QQmlMetaTypeData *data = metaTypeData(); + QQmlMetaTypeDataPtr data; if (data->stringConverters.contains(type)) return; data->stringConverters.insert(type, converter); @@ -2317,9 +1069,7 @@ void QQmlMetaType::registerCustomStringConverter(int type, StringConverter conve */ QQmlMetaType::StringConverter QQmlMetaType::customStringConverter(int type) { - QMutexLocker lock(metaTypeDataLock()); - - QQmlMetaTypeData *data = metaTypeData(); + const QQmlMetaTypeDataPtr data; return data->stringConverters.value(type); } @@ -2346,8 +1096,7 @@ QQmlType QQmlMetaType::qmlType(const QString &qualifiedName, int version_major, QQmlType QQmlMetaType::qmlType(const QHashedStringRef &name, const QHashedStringRef &module, int version_major, int version_minor) { Q_ASSERT(version_major >= 0 && version_minor >= 0); - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + const QQmlMetaTypeDataPtr data; QQmlMetaTypeData::Names::ConstIterator it = data->nameToType.constFind(name); while (it != data->nameToType.cend() && it.key() == name) { @@ -2367,9 +1116,7 @@ QQmlType QQmlMetaType::qmlType(const QHashedStringRef &name, const QHashedString */ QQmlType QQmlMetaType::qmlType(const QMetaObject *metaObject) { - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); - + const QQmlMetaTypeDataPtr data; return QQmlType(data->metaObjectToType.value(metaObject)); } @@ -2381,8 +1128,7 @@ QQmlType QQmlMetaType::qmlType(const QMetaObject *metaObject) QQmlType QQmlMetaType::qmlType(const QMetaObject *metaObject, const QHashedStringRef &module, int version_major, int version_minor) { Q_ASSERT(version_major >= 0 && version_minor >= 0); - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + const QQmlMetaTypeDataPtr data; QQmlMetaTypeData::MetaObjects::const_iterator it = data->metaObjectToType.constFind(metaObject); while (it != data->metaObjectToType.cend() && it.key() == metaObject) { @@ -2402,8 +1148,7 @@ QQmlType QQmlMetaType::qmlType(const QMetaObject *metaObject, const QHashedStrin */ QQmlType QQmlMetaType::qmlType(int typeId, TypeIdCategory category) { - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + const QQmlMetaTypeDataPtr data; if (category == TypeIdCategory::MetaType) { QQmlTypePrivate *type = data->idToType.value(typeId); @@ -2426,8 +1171,7 @@ QQmlType QQmlMetaType::qmlType(int typeId, TypeIdCategory category) QQmlType QQmlMetaType::qmlType(const QUrl &unNormalizedUrl, bool includeNonFileImports /* = false */) { const QUrl url = QQmlTypeLoader::normalize(unNormalizedUrl); - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + const QQmlMetaTypeDataPtr data; QQmlType type(data->urlToType.value(url)); if (!type.isValid() && includeNonFileImports) @@ -2439,215 +1183,79 @@ QQmlType QQmlMetaType::qmlType(const QUrl &unNormalizedUrl, bool includeNonFileI return QQmlType(); } -QQmlPropertyCache *QQmlMetaTypeData::propertyCache(const QMetaObject *metaObject, int minorVersion) -{ - if (QQmlPropertyCache *rv = propertyCaches.value(metaObject)) - return rv; - - if (!metaObject->superClass()) { - QQmlPropertyCache *rv = new QQmlPropertyCache(metaObject); - propertyCaches.insert(metaObject, rv); - return rv; - } - QQmlPropertyCache *super = propertyCache(metaObject->superClass(), minorVersion); - QQmlPropertyCache *rv = super->copyAndAppend(metaObject, minorVersion); - propertyCaches.insert(metaObject, rv); - return rv; -} - QQmlPropertyCache *QQmlMetaType::propertyCache(const QMetaObject *metaObject, int minorVersion) { - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + QQmlMetaTypeDataPtr data; // not const: the cache is created on demand return data->propertyCache(metaObject, minorVersion); } -QQmlPropertyCache *QQmlMetaTypeData::propertyCache(const QQmlType &type, int minorVersion) -{ - Q_ASSERT(type.isValid()); - - if (QQmlPropertyCache *pc = type.key()->propertyCacheForMinorVersion(minorVersion)) - return pc; - - QVector<QQmlType> types; - - int maxMinorVersion = 0; - - const QMetaObject *metaObject = type.metaObject(); - - while (metaObject) { - QQmlType t = QQmlMetaType::qmlType(metaObject, type.module(), type.majorVersion(), minorVersion); - if (t.isValid()) { - maxMinorVersion = qMax(maxMinorVersion, t.minorVersion()); - types << t; - } else { - types << QQmlType(); - } - - metaObject = metaObject->superClass(); - } - - if (QQmlPropertyCache *pc = type.key()->propertyCacheForMinorVersion(maxMinorVersion)) { - const_cast<QQmlTypePrivate*>(type.key())->setPropertyCacheForMinorVersion(minorVersion, pc); - return pc; - } - - QQmlPropertyCache *raw = propertyCache(type.metaObject(), minorVersion); - - bool hasCopied = false; - - for (int ii = 0; ii < types.count(); ++ii) { - QQmlType currentType = types.at(ii); - if (!currentType.isValid()) - continue; - - int rev = currentType.metaObjectRevision(); - int moIndex = types.count() - 1 - ii; - - if (raw->allowedRevisionCache[moIndex] != rev) { - if (!hasCopied) { - raw = raw->copy(); - hasCopied = true; - } - raw->allowedRevisionCache[moIndex] = rev; - } - } - - // Test revision compatibility - the basic rule is: - // * Anything that is excluded, cannot overload something that is not excluded * - - // Signals override: - // * other signals and methods of the same name. - // * properties named on<Signal Name> - // * automatic <property name>Changed notify signals - - // Methods override: - // * other methods of the same name - - // Properties override: - // * other elements of the same name - -#if 0 - bool overloadError = false; - QString overloadName; - - for (QQmlPropertyCache::StringCache::ConstIterator iter = raw->stringCache.begin(); - !overloadError && iter != raw->stringCache.end(); - ++iter) { - - QQmlPropertyData *d = *iter; - if (raw->isAllowedInRevision(d)) - continue; // Not excluded - no problems - - // check that a regular "name" overload isn't happening - QQmlPropertyData *current = d; - while (!overloadError && current) { - current = d->overrideData(current); - if (current && raw->isAllowedInRevision(current)) - overloadError = true; - } - } - - if (overloadError) { - if (hasCopied) raw->release(); - - error.setDescription(QLatin1String("Type ") + type.qmlTypeName() + QLatin1Char(' ') + QString::number(type.majorVersion()) + QLatin1Char('.') + QString::number(minorVersion) + QLatin1String(" contains an illegal property \"") + overloadName + QLatin1String("\". This is an error in the type's implementation.")); - return 0; - } -#endif - - const_cast<QQmlTypePrivate*>(type.key())->setPropertyCacheForMinorVersion(minorVersion, raw); - - if (hasCopied) - raw->release(); - - if (minorVersion != maxMinorVersion) - const_cast<QQmlTypePrivate*>(type.key())->setPropertyCacheForMinorVersion(maxMinorVersion, raw); - - return raw; -} - QQmlPropertyCache *QQmlMetaType::propertyCache(const QQmlType &type, int minorVersion) { - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + QQmlMetaTypeDataPtr data; // not const: the cache is created on demand return data->propertyCache(type, minorVersion); } -void qmlUnregisterType(int typeIndex) +void QQmlMetaType::unregisterType(int typeIndex) { - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); - { - const QQmlTypePrivate *d = data->types.value(typeIndex).priv(); - if (d) { - removeQQmlTypePrivate(data->idToType, d); - removeQQmlTypePrivate(data->nameToType, d); - removeQQmlTypePrivate(data->urlToType, d); - removeQQmlTypePrivate(data->urlToNonFileImportType, d); - removeQQmlTypePrivate(data->metaObjectToType, d); - for (QQmlMetaTypeData::TypeModules::Iterator module = data->uriToModule.begin(); module != data->uriToModule.end(); ++module) { - QQmlTypeModulePrivate *modulePrivate = (*module)->priv(); - modulePrivate->remove(d); - } - data->types[typeIndex] = QQmlType(); - } + QQmlMetaTypeDataPtr data; + if (const QQmlTypePrivate *d = data->types.value(typeIndex).priv()) { + removeQQmlTypePrivate(data->idToType, d); + removeQQmlTypePrivate(data->nameToType, d); + removeQQmlTypePrivate(data->urlToType, d); + removeQQmlTypePrivate(data->urlToNonFileImportType, d); + removeQQmlTypePrivate(data->metaObjectToType, d); + for (auto & module : data->uriToModule) + module->remove(d); + data->types[typeIndex] = QQmlType(); } } void QQmlMetaType::freeUnusedTypesAndCaches() { - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); - - { - bool deletedAtLeastOneType; - do { - deletedAtLeastOneType = false; - QList<QQmlType>::Iterator it = data->types.begin(); - while (it != data->types.end()) { - const QQmlTypePrivate *d = (*it).priv(); - if (d && d->refCount == 1) { - deletedAtLeastOneType = true; - - removeQQmlTypePrivate(data->idToType, d); - removeQQmlTypePrivate(data->nameToType, d); - removeQQmlTypePrivate(data->urlToType, d); - removeQQmlTypePrivate(data->urlToNonFileImportType, d); - removeQQmlTypePrivate(data->metaObjectToType, d); - - for (QQmlMetaTypeData::TypeModules::Iterator module = data->uriToModule.begin(); module != data->uriToModule.end(); ++module) { - QQmlTypeModulePrivate *modulePrivate = (*module)->priv(); - modulePrivate->remove(d); - } - - *it = QQmlType(); - } else { - ++it; - } + QQmlMetaTypeDataPtr data; + + bool deletedAtLeastOneType; + do { + deletedAtLeastOneType = false; + QList<QQmlType>::Iterator it = data->types.begin(); + while (it != data->types.end()) { + const QQmlTypePrivate *d = (*it).priv(); + if (d && d->count() == 1) { + deletedAtLeastOneType = true; + + removeQQmlTypePrivate(data->idToType, d); + removeQQmlTypePrivate(data->nameToType, d); + removeQQmlTypePrivate(data->urlToType, d); + removeQQmlTypePrivate(data->urlToNonFileImportType, d); + removeQQmlTypePrivate(data->metaObjectToType, d); + + for (auto &module : data->uriToModule) + module->remove(d); + + *it = QQmlType(); + } else { + ++it; } - } while (deletedAtLeastOneType); - } - - { - bool deletedAtLeastOneCache; - do { - deletedAtLeastOneCache = false; - QHash<const QMetaObject *, QQmlPropertyCache *>::Iterator it = data->propertyCaches.begin(); - while (it != data->propertyCaches.end()) { - - if ((*it)->count() == 1) { - QQmlPropertyCache *pc = nullptr; - qSwap(pc, *it); - it = data->propertyCaches.erase(it); - pc->release(); - deletedAtLeastOneCache = true; - } else { - ++it; - } + } + } while (deletedAtLeastOneType); + + bool deletedAtLeastOneCache; + do { + deletedAtLeastOneCache = false; + QHash<const QMetaObject *, QQmlPropertyCache *>::Iterator it = data->propertyCaches.begin(); + while (it != data->propertyCaches.end()) { + + if ((*it)->count() == 1) { + QQmlPropertyCache *pc = nullptr; + qSwap(pc, *it); + it = data->propertyCaches.erase(it); + pc->release(); + deletedAtLeastOneCache = true; + } else { + ++it; } - } while (deletedAtLeastOneCache); - } + } + } while (deletedAtLeastOneCache); } /*! @@ -2655,8 +1263,7 @@ void QQmlMetaType::freeUnusedTypesAndCaches() */ QList<QString> QQmlMetaType::qmlTypeNames() { - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + const QQmlMetaTypeDataPtr data; QList<QString> names; names.reserve(data->nameToType.count()); @@ -2675,8 +1282,7 @@ QList<QString> QQmlMetaType::qmlTypeNames() */ QList<QQmlType> QQmlMetaType::qmlTypes() { - QMutexLocker lock(metaTypeDataLock()); - const QQmlMetaTypeData *data = metaTypeData(); + const QQmlMetaTypeDataPtr data; QList<QQmlType> types; for (QQmlTypePrivate *t : data->nameToType) @@ -2690,9 +1296,7 @@ QList<QQmlType> QQmlMetaType::qmlTypes() */ QList<QQmlType> QQmlMetaType::qmlAllTypes() { - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); - + const QQmlMetaTypeDataPtr data; return data->types; } @@ -2701,8 +1305,7 @@ QList<QQmlType> QQmlMetaType::qmlAllTypes() */ QList<QQmlType> QQmlMetaType::qmlSingletonTypes() { - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + const QQmlMetaTypeDataPtr data; QList<QQmlType> retn; for (const auto t : qAsConst(data->nameToType)) { @@ -2715,8 +1318,7 @@ QList<QQmlType> QQmlMetaType::qmlSingletonTypes() const QV4::CompiledData::Unit *QQmlMetaType::findCachedCompilationUnit(const QUrl &uri, CachedUnitLookupError *status) { - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + const QQmlMetaTypeDataPtr data; for (const auto lookup : qAsConst(data->lookupCachedQmlUnit)) { if (const QQmlPrivate::CachedQmlUnit *unit = lookup(uri)) { @@ -2741,15 +1343,13 @@ const QV4::CompiledData::Unit *QQmlMetaType::findCachedCompilationUnit(const QUr void QQmlMetaType::prependCachedUnitLookupFunction(QQmlPrivate::QmlUnitCacheLookupFunction handler) { - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + QQmlMetaTypeDataPtr data; data->lookupCachedQmlUnit.prepend(handler); } void QQmlMetaType::removeCachedUnitLookupFunction(QQmlPrivate::QmlUnitCacheLookupFunction handler) { - QMutexLocker lock(metaTypeDataLock()); - QQmlMetaTypeData *data = metaTypeData(); + QQmlMetaTypeDataPtr data; data->lookupCachedQmlUnit.removeAll(handler); } @@ -2795,4 +1395,38 @@ QString QQmlMetaType::prettyTypeName(const QObject *object) return typeName; } +QList<QQmlProxyMetaObject::ProxyData> QQmlMetaType::proxyData(const QMetaObject *mo, + const QMetaObject *baseMetaObject, + QMetaObject *lastMetaObject) +{ + QList<QQmlProxyMetaObject::ProxyData> metaObjects; + mo = mo->d.superdata; + + const QQmlMetaTypeDataPtr data; + + while (mo) { + QQmlTypePrivate *t = data->metaObjectToType.value(mo); + if (t) { + if (t->regType == QQmlType::CppType) { + if (t->extraData.cd->extFunc) { + QMetaObjectBuilder builder; + clone(builder, t->extraData.cd->extMetaObject, t->baseMetaObject, baseMetaObject); + builder.setFlags(QMetaObjectBuilder::DynamicMetaObject); + QMetaObject *mmo = builder.toMetaObject(); + mmo->d.superdata = baseMetaObject; + if (!metaObjects.isEmpty()) + metaObjects.constLast().metaObject->d.superdata = mmo; + else if (lastMetaObject) + lastMetaObject->d.superdata = mmo; + QQmlProxyMetaObject::ProxyData data = { mmo, t->extraData.cd->extFunc, 0, 0 }; + metaObjects << data; + } + } + } + mo = mo->d.superdata; + } + + return metaObjects; +} + QT_END_NAMESPACE diff --git a/src/qml/qml/qqmlmetatype_p.h b/src/qml/qml/qqmlmetatype_p.h index 3ad2de7bb3..dde9cf68d7 100644 --- a/src/qml/qml/qqmlmetatype_p.h +++ b/src/qml/qml/qqmlmetatype_p.h @@ -51,41 +51,46 @@ // We mean it. // -#include "qqml.h" #include <private/qtqmlglobal_p.h> - -#include <QtCore/qglobal.h> -#include <QtCore/qvariant.h> -#include <QtCore/qbitarray.h> -#include <QtQml/qjsvalue.h> +#include <private/qqmltype_p.h> +#include <private/qqmlproxymetaobject_p.h> QT_BEGIN_NAMESPACE -class QQmlType; -class QQmlEngine; -class QQmlEnginePrivate; -class QQmlCustomParser; -class QQmlTypePrivate; class QQmlTypeModule; -class QHashedString; -class QHashedStringRef; class QMutex; -class QQmlPropertyCache; -class QQmlCompiledData; - -namespace QV4 { struct String; } - -void Q_QML_PRIVATE_EXPORT qmlUnregisterType(int type); +class QQmlError; class Q_QML_PRIVATE_EXPORT QQmlMetaType { public: + static QQmlType registerType(const QQmlPrivate::RegisterType &type); + static QQmlType registerInterface(const QQmlPrivate::RegisterInterface &type); + static QQmlType registerSingletonType(const QQmlPrivate::RegisterSingletonType &type); static QQmlType registerCompositeSingletonType(const QQmlPrivate::RegisterCompositeSingletonType &type); static QQmlType registerCompositeType(const QQmlPrivate::RegisterCompositeType &type); + static bool registerPluginTypes(QObject *instance, const QString &basePath, + const QString &uri, const QString &typeNamespace, int vmaj, + QList<QQmlError> *errors); + static QQmlType typeForUrl(const QString &urlString, const QHashedStringRef& typeName, + bool isCompositeSingleton, QList<QQmlError> *errors, + int majorVersion = -1, int minorVersion = -1); + + static void unregisterType(int type); static void registerInternalCompositeType(QV4::CompiledData::CompilationUnit *compilationUnit); static void unregisterInternalCompositeType(QV4::CompiledData::CompilationUnit *compilationUnit); + static void registerModule(const char *uri, int versionMajor, int versionMinor); + static bool protectModule(const char *uri, int majVersion); + + static int typeId(const char *uri, int versionMajor, int versionMinor, const char *qmlName); + + static void registerUndeletableType(const QQmlType &dtype); + + static int registerAttachedPropertyId(const QMetaObject *metaObject, int index); + static bool unregisterAttachedPropertyId(const QMetaObject *metaObject, int index); + static QList<QString> qmlTypeNames(); static QList<QQmlType> qmlTypes(); static QList<QQmlType> qmlSingletonTypes(); @@ -150,228 +155,36 @@ public: static void prependCachedUnitLookupFunction(QQmlPrivate::QmlUnitCacheLookupFunction handler); static void removeCachedUnitLookupFunction(QQmlPrivate::QmlUnitCacheLookupFunction handler); - static bool namespaceContainsRegistrations(const QString &, int majorVersion); - - static void protectNamespace(const QString &); - - static void setTypeRegistrationNamespace(const QString &); - static QMutex *typeRegistrationLock(); static QString prettyTypeName(const QObject *object); -}; -struct QQmlMetaTypeData; -class QHashedCStringRef; -class QQmlPropertyCache; -class Q_QML_PRIVATE_EXPORT QQmlType -{ -public: - QQmlType(); - QQmlType(const QQmlType &other); - QQmlType &operator =(const QQmlType &other); - explicit QQmlType(QQmlTypePrivate *priv); - ~QQmlType(); - - bool operator ==(const QQmlType &other) const { - return d == other.d; + template <typename QQmlTypeContainer> + static void removeQQmlTypePrivate(QQmlTypeContainer &container, + const QQmlTypePrivate *reference) + { + for (typename QQmlTypeContainer::iterator it = container.begin(); it != container.end();) { + if (*it == reference) + it = container.erase(it); + else + ++it; + } } - bool isValid() const { return d != nullptr; } - const QQmlTypePrivate *key() const { return d; } - - QByteArray typeName() const; - QString qmlTypeName() const; - QString elementName() const; - - QHashedString module() const; - int majorVersion() const; - int minorVersion() const; - - bool availableInVersion(int vmajor, int vminor) const; - bool availableInVersion(const QHashedStringRef &module, int vmajor, int vminor) const; - - QObject *create() const; - void create(QObject **, void **, size_t) const; - - typedef void (*CreateFunc)(void *); - CreateFunc createFunction() const; - QQmlCustomParser *customParser() const; - - bool isCreatable() const; - typedef QObject *(*ExtensionFunc)(QObject *); - ExtensionFunc extensionFunction() const; - bool isExtendedType() const; - QString noCreationReason() const; + static int registerAutoParentFunction(QQmlPrivate::RegisterAutoParent &autoparent); + static int registerUnitCacheHook(const QQmlPrivate::RegisterQmlUnitCacheHook &hookRegistration); + static void clearTypeRegistrations(); - bool isSingleton() const; - bool isInterface() const; - bool isComposite() const; - bool isCompositeSingleton() const; + static QList<QQmlProxyMetaObject::ProxyData> proxyData(const QMetaObject *mo, + const QMetaObject *baseMetaObject, + QMetaObject *lastMetaObject); - int typeId() const; - int qListTypeId() const; - - const QMetaObject *metaObject() const; - const QMetaObject *baseMetaObject() const; - int metaObjectRevision() const; - bool containsRevisionedAttributes() const; - - QQmlAttachedPropertiesFunc attachedPropertiesFunction(QQmlEnginePrivate *engine) const; - const QMetaObject *attachedPropertiesType(QQmlEnginePrivate *engine) const; - int attachedPropertiesId(QQmlEnginePrivate *engine) const; - - int parserStatusCast() const; - const char *interfaceIId() const; - int propertyValueSourceCast() const; - int propertyValueInterceptorCast() const; - - int index() const; - - class Q_QML_PRIVATE_EXPORT SingletonInstanceInfo - { - public: - SingletonInstanceInfo() - : scriptCallback(nullptr), qobjectCallback(nullptr), instanceMetaObject(nullptr) {} - - QJSValue (*scriptCallback)(QQmlEngine *, QJSEngine *); - QObject *(*qobjectCallback)(QQmlEngine *, QJSEngine *); - const QMetaObject *instanceMetaObject; - QString typeName; - QUrl url; // used by composite singletons - - void setQObjectApi(QQmlEngine *, QObject *); - QObject *qobjectApi(QQmlEngine *) const; - void setScriptApi(QQmlEngine *, const QJSValue &); - QJSValue scriptApi(QQmlEngine *) const; - - void init(QQmlEngine *); - void destroy(QQmlEngine *); - - QHash<QQmlEngine *, QJSValue> scriptApis; - QHash<QQmlEngine *, QObject *> qobjectApis; - }; - SingletonInstanceInfo *singletonInstanceInfo() const; - - QUrl sourceUrl() const; - - int enumValue(QQmlEnginePrivate *engine, const QHashedStringRef &, bool *ok) const; - int enumValue(QQmlEnginePrivate *engine, const QHashedCStringRef &, bool *ok) const; - int enumValue(QQmlEnginePrivate *engine, const QV4::String *, bool *ok) const; - - int scopedEnumIndex(QQmlEnginePrivate *engine, const QV4::String *, bool *ok) const; - int scopedEnumIndex(QQmlEnginePrivate *engine, const QString &, bool *ok) const; - int scopedEnumValue(QQmlEnginePrivate *engine, int index, const QV4::String *, bool *ok) const; - int scopedEnumValue(QQmlEnginePrivate *engine, int index, const QString &, bool *ok) const; - int scopedEnumValue(QQmlEnginePrivate *engine, const QByteArray &, const QByteArray &, bool *ok) const; - int scopedEnumValue(QQmlEnginePrivate *engine, const QStringRef &, const QStringRef &, bool *ok) const; - - QQmlTypePrivate *priv() const { return d; } - static void refHandle(QQmlTypePrivate *priv); - static void derefHandle(QQmlTypePrivate *priv); - static int refCount(QQmlTypePrivate *priv); - - enum RegistrationType { - CppType = 0, - SingletonType = 1, - InterfaceType = 2, - CompositeType = 3, - CompositeSingletonType = 4, - AnyRegistrationType = 255 - }; - -private: - QQmlType superType() const; - QQmlType resolveCompositeBaseType(QQmlEnginePrivate *engine) const; - int resolveCompositeEnumValue(QQmlEnginePrivate *engine, const QString &name, bool *ok) const; - QQmlPropertyCache *compositePropertyCache(QQmlEnginePrivate *engine) const; - friend class QQmlTypePrivate; - - friend QString registrationTypeString(RegistrationType); - friend bool checkRegistration(RegistrationType, QQmlMetaTypeData *, const char *, const QString &, int); - friend QQmlType registerType(const QQmlPrivate::RegisterType &); - friend QQmlType registerSingletonType(const QQmlPrivate::RegisterSingletonType &); - friend QQmlType registerInterface(const QQmlPrivate::RegisterInterface &); - friend int registerQmlUnitCacheHook(const QQmlPrivate::RegisterQmlUnitCacheHook &); - friend uint qHash(const QQmlType &t, uint seed); - friend Q_QML_EXPORT void qmlClearTypeRegistrations(); - friend class QQmlMetaType; - - QQmlType(QQmlMetaTypeData *data, const QQmlPrivate::RegisterInterface &); - QQmlType(QQmlMetaTypeData *data, const QString &, const QQmlPrivate::RegisterSingletonType &); - QQmlType(QQmlMetaTypeData *data, const QString &, const QQmlPrivate::RegisterType &); - QQmlType(QQmlMetaTypeData *data, const QString &, const QQmlPrivate::RegisterCompositeType &); - QQmlType(QQmlMetaTypeData *data, const QString &, const QQmlPrivate::RegisterCompositeSingletonType &); - - QQmlTypePrivate *d; + static void clone(QMetaObjectBuilder &builder, const QMetaObject *mo, + const QMetaObject *ignoreStart, const QMetaObject *ignoreEnd); }; Q_DECLARE_TYPEINFO(QQmlMetaType, Q_MOVABLE_TYPE); - -inline uint qHash(const QQmlType &t, uint seed = 0) { return qHash(reinterpret_cast<quintptr>(t.d), seed); } - - -class QQmlTypeModulePrivate; -class QQmlTypeModule -{ -public: - QString module() const; - int majorVersion() const; - - int minimumMinorVersion() const; - int maximumMinorVersion() const; - - QQmlType type(const QHashedStringRef &, int) const; - QQmlType type(const QV4::String *, int) const; - - void walkCompositeSingletons(const std::function<void(const QQmlType &)> &callback) const; - - QQmlTypeModulePrivate *priv() { return d; } -private: - //Used by register functions and creates the QQmlTypeModule for them - friend QQmlTypeModule *getTypeModule(const QHashedString &uri, int majorVersion, QQmlMetaTypeData *data); - friend void addTypeToData(QQmlTypePrivate *type, QQmlMetaTypeData *data); - friend struct QQmlMetaTypeData; - friend Q_QML_EXPORT void qmlClearTypeRegistrations(); - friend class QQmlTypeModulePrivate; - - QQmlTypeModule(); - ~QQmlTypeModule(); - QQmlTypeModulePrivate *d; -}; - -class QQmlTypeModuleVersion -{ -public: - QQmlTypeModuleVersion(); - QQmlTypeModuleVersion(QQmlTypeModule *, int); - QQmlTypeModuleVersion(const QQmlTypeModuleVersion &); - QQmlTypeModuleVersion &operator=(const QQmlTypeModuleVersion &); - - QQmlTypeModule *module() const; - int minorVersion() const; - - QQmlType type(const QHashedStringRef &) const; - QQmlType type(const QV4::String *) const; - -private: - QQmlTypeModule *m_module; - int m_minor; -}; - -class Q_AUTOTEST_EXPORT QQmlMetaTypeRegistrationFailureRecorder -{ - QStringList _failures; - -public: - QQmlMetaTypeRegistrationFailureRecorder(); - ~QQmlMetaTypeRegistrationFailureRecorder(); - - QStringList failures() const - { return _failures; } -}; - QT_END_NAMESPACE #endif // QQMLMETATYPE_P_H diff --git a/src/qml/qml/qqmlmetatypedata.cpp b/src/qml/qml/qqmlmetatypedata.cpp new file mode 100644 index 0000000000..13c46e4911 --- /dev/null +++ b/src/qml/qml/qqmlmetatypedata.cpp @@ -0,0 +1,204 @@ +/**************************************************************************** +** +** 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 "qqmlmetatypedata_p.h" + +#include <private/qqmltype_p_p.h> +#include <private/qqmltypemodule_p.h> +#include <private/qqmlpropertycache_p.h> + +QT_BEGIN_NAMESPACE + +QQmlMetaTypeData::QQmlMetaTypeData() +{ +} + +QQmlMetaTypeData::~QQmlMetaTypeData() +{ + for (TypeModules::const_iterator i = uriToModule.constBegin(), cend = uriToModule.constEnd(); i != cend; ++i) + delete *i; + for (QHash<const QMetaObject *, QQmlPropertyCache *>::Iterator it = propertyCaches.begin(), end = propertyCaches.end(); + it != end; ++it) + (*it)->release(); + + // Do this before the attached properties disappear. + types.clear(); + undeletableTypes.clear(); +} + +// This expects a "fresh" QQmlTypePrivate and adopts its reference. +void QQmlMetaTypeData::registerType(QQmlTypePrivate *priv) +{ + for (int i = 0; i < types.count(); ++i) { + if (!types.at(i).isValid()) { + types[i] = QQmlType(priv); + priv->index = i; + return; + } + } + types.append(QQmlType(priv)); + priv->index = types.count() - 1; + priv->release(); +} + +QQmlPropertyCache *QQmlMetaTypeData::propertyCache(const QMetaObject *metaObject, int minorVersion) +{ + if (QQmlPropertyCache *rv = propertyCaches.value(metaObject)) + return rv; + + if (!metaObject->superClass()) { + QQmlPropertyCache *rv = new QQmlPropertyCache(metaObject); + propertyCaches.insert(metaObject, rv); + return rv; + } + QQmlPropertyCache *super = propertyCache(metaObject->superClass(), minorVersion); + QQmlPropertyCache *rv = super->copyAndAppend(metaObject, minorVersion); + propertyCaches.insert(metaObject, rv); + return rv; +} + +QQmlPropertyCache *QQmlMetaTypeData::propertyCache(const QQmlType &type, int minorVersion) +{ + Q_ASSERT(type.isValid()); + + if (QQmlPropertyCache *pc = type.key()->propertyCacheForMinorVersion(minorVersion)) + return pc; + + QVector<QQmlType> types; + + int maxMinorVersion = 0; + + const QMetaObject *metaObject = type.metaObject(); + + while (metaObject) { + QQmlType t = QQmlMetaType::qmlType(metaObject, type.module(), type.majorVersion(), minorVersion); + if (t.isValid()) { + maxMinorVersion = qMax(maxMinorVersion, t.minorVersion()); + types << t; + } else { + types << QQmlType(); + } + + metaObject = metaObject->superClass(); + } + + if (QQmlPropertyCache *pc = type.key()->propertyCacheForMinorVersion(maxMinorVersion)) { + const_cast<QQmlTypePrivate*>(type.key())->setPropertyCacheForMinorVersion(minorVersion, pc); + return pc; + } + + QQmlPropertyCache *raw = propertyCache(type.metaObject(), minorVersion); + + bool hasCopied = false; + + for (int ii = 0; ii < types.count(); ++ii) { + QQmlType currentType = types.at(ii); + if (!currentType.isValid()) + continue; + + int rev = currentType.metaObjectRevision(); + int moIndex = types.count() - 1 - ii; + + if (raw->allowedRevision(moIndex) != rev) { + if (!hasCopied) { + // TODO: The copy should be mutable, and the original should be const + // Considering this, the setAllowedRevision() below does not violate + // the immutability of already published property caches. + raw = raw->copy(); + hasCopied = true; + } + raw->setAllowedRevision(moIndex, rev); + } + } + + // Test revision compatibility - the basic rule is: + // * Anything that is excluded, cannot overload something that is not excluded * + + // Signals override: + // * other signals and methods of the same name. + // * properties named on<Signal Name> + // * automatic <property name>Changed notify signals + + // Methods override: + // * other methods of the same name + + // Properties override: + // * other elements of the same name + +#if 0 + bool overloadError = false; + QString overloadName; + + for (QQmlPropertyCache::StringCache::ConstIterator iter = raw->stringCache.begin(); + !overloadError && iter != raw->stringCache.end(); + ++iter) { + + QQmlPropertyData *d = *iter; + if (raw->isAllowedInRevision(d)) + continue; // Not excluded - no problems + + // check that a regular "name" overload isn't happening + QQmlPropertyData *current = d; + while (!overloadError && current) { + current = d->overrideData(current); + if (current && raw->isAllowedInRevision(current)) + overloadError = true; + } + } + + if (overloadError) { + if (hasCopied) raw->release(); + + error.setDescription(QLatin1String("Type ") + type.qmlTypeName() + QLatin1Char(' ') + QString::number(type.majorVersion()) + QLatin1Char('.') + QString::number(minorVersion) + QLatin1String(" contains an illegal property \"") + overloadName + QLatin1String("\". This is an error in the type's implementation.")); + return 0; + } +#endif + + const_cast<QQmlTypePrivate*>(type.key())->setPropertyCacheForMinorVersion(minorVersion, raw); + + if (hasCopied) + raw->release(); + + if (minorVersion != maxMinorVersion) + const_cast<QQmlTypePrivate*>(type.key())->setPropertyCacheForMinorVersion(maxMinorVersion, raw); + + return raw; +} + +QT_END_NAMESPACE diff --git a/src/qml/qml/qqmlmetatypedata_p.h b/src/qml/qml/qqmlmetatypedata_p.h new file mode 100644 index 0000000000..2c5a32be1b --- /dev/null +++ b/src/qml/qml/qqmlmetatypedata_p.h @@ -0,0 +1,161 @@ +/**************************************************************************** +** +** 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 QQMLMETATYPEDATA_P_H +#define QQMLMETATYPEDATA_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 <private/qqmltype_p.h> +#include <private/qqmlmetatype_p.h> +#include <private/qhashedstring_p.h> + +#include <QtCore/qset.h> +#include <QtCore/qvector.h> +#include <QtCore/qbitarray.h> + +QT_BEGIN_NAMESPACE + +class QQmlTypePrivate; +struct QQmlMetaTypeData +{ + QQmlMetaTypeData(); + ~QQmlMetaTypeData(); + void registerType(QQmlTypePrivate *priv); + QList<QQmlType> types; + QSet<QQmlType> undeletableTypes; + typedef QHash<int, QQmlTypePrivate *> Ids; + Ids idToType; + typedef QHash<QHashedStringRef, QQmlTypePrivate *> Names; + Names nameToType; + typedef QHash<QUrl, QQmlTypePrivate *> Files; //For file imported composite types only + Files urlToType; + Files urlToNonFileImportType; // For non-file imported composite and composite + // singleton types. This way we can locate any + // of them by url, even if it was registered as + // a module via QQmlPrivate::RegisterCompositeType + typedef QHash<const QMetaObject *, QQmlTypePrivate *> MetaObjects; + MetaObjects metaObjectToType; + typedef QHash<int, QQmlMetaType::StringConverter> StringConverters; + StringConverters stringConverters; + + struct VersionedUri { + VersionedUri() + : majorVersion(0) {} + VersionedUri(const QHashedString &uri, int majorVersion) + : uri(uri), majorVersion(majorVersion) {} + bool operator==(const VersionedUri &other) const { + return other.majorVersion == majorVersion && other.uri == uri; + } + QHashedString uri; + int majorVersion; + }; + typedef QHash<VersionedUri, QQmlTypeModule *> TypeModules; + TypeModules uriToModule; + + QBitArray objects; + QBitArray interfaces; + QBitArray lists; + + QList<QQmlPrivate::AutoParentFunction> parentFunctions; + QVector<QQmlPrivate::QmlUnitCacheLookupFunction> lookupCachedQmlUnit; + + QSet<QString> protectedNamespaces; + + QString typeRegistrationNamespace; + + QHash<int, int> qmlLists; + + QHash<const QMetaObject *, QQmlPropertyCache *> propertyCaches; + QQmlPropertyCache *propertyCache(const QMetaObject *metaObject, int minorVersion); + QQmlPropertyCache *propertyCache(const QQmlType &type, int minorVersion); + + void setTypeRegistrationFailures(QStringList *failures) + { + m_typeRegistrationFailures = failures; + } + + void recordTypeRegFailure(const QString &message) + { + if (m_typeRegistrationFailures) + m_typeRegistrationFailures->append(message); + else + qWarning("%s", message.toUtf8().constData()); + } + + int attachedPropertyId(const QMetaObject *metaObject, int ownIndex) + { + auto iter = attachedPropertyIds.find(metaObject); + return (iter == attachedPropertyIds.end()) + ? *attachedPropertyIds.insert(metaObject, ownIndex) + : *iter; + } + + bool removeAttachedPropertyId(const QMetaObject *metaObject, int ownIndex) + { + auto iter = attachedPropertyIds.find(metaObject); + if (iter != attachedPropertyIds.end() && *iter == ownIndex) { + attachedPropertyIds.erase(iter); + return true; + } + return false; + } + +private: + QStringList *m_typeRegistrationFailures = nullptr; + QHash<const QMetaObject *, int> attachedPropertyIds; +}; + +inline uint qHash(const QQmlMetaTypeData::VersionedUri &v) +{ + return v.uri.hash() ^ qHash(v.majorVersion); +} + +QT_END_NAMESPACE + +#endif // QQMLMETATYPEDATA_P_H diff --git a/src/qml/qml/qqmlobjectorgadget.cpp b/src/qml/qml/qqmlobjectorgadget.cpp new file mode 100644 index 0000000000..1d4916d7d1 --- /dev/null +++ b/src/qml/qml/qqmlobjectorgadget.cpp @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** 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 "qqmlobjectorgadget_p.h" + +QT_BEGIN_NAMESPACE + +void QQmlObjectOrGadget::metacall(QMetaObject::Call type, int index, void **argv) const +{ + if (ptr.isNull()) { + const QMetaObject *metaObject = _m.asT2(); + metaObject->d.static_metacall(nullptr, type, index, argv); + } + else if (ptr.isT1()) { + QMetaObject::metacall(ptr.asT1(), type, index, argv); + } + else { + const QMetaObject *metaObject = _m.asT1()->metaObject(); + QQmlMetaObject::resolveGadgetMethodOrPropertyIndex(type, &metaObject, &index); + metaObject->d.static_metacall(reinterpret_cast<QObject*>(ptr.asT2()), type, index, argv); + } +} + +QT_END_NAMESPACE diff --git a/src/qml/qml/qqmlobjectorgadget_p.h b/src/qml/qml/qqmlobjectorgadget_p.h new file mode 100644 index 0000000000..c5f5f58a3a --- /dev/null +++ b/src/qml/qml/qqmlobjectorgadget_p.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** 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 QQMLOBJECTORGADGET_P_H +#define QQMLOBJECTORGADGET_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 <private/qqmlmetaobject_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlObjectOrGadget: public QQmlMetaObject +{ +public: + QQmlObjectOrGadget(QObject *obj) + : QQmlMetaObject(obj), + ptr(obj) + {} + QQmlObjectOrGadget(QQmlPropertyCache *propertyCache, void *gadget) + : QQmlMetaObject(propertyCache) + , ptr(gadget) + {} + + void metacall(QMetaObject::Call type, int index, void **argv) const; + +private: + QBiPointer<QObject, void> ptr; + +protected: + QQmlObjectOrGadget(const QMetaObject* metaObject) + : QQmlMetaObject(metaObject) + {} +}; + +QT_END_NAMESPACE + +#endif // QQMLOBJECTORGADGET_P_H 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 <private/qqmlpropertycache_p.h> #include <private/qqmldata_p.h> #include <private/qmetaobjectbuilder_p.h> -#include <private/qv8engine_p.h> #include <qqmlengine.h> #include <qdebug.h> diff --git a/src/qml/qml/qqmlproperty_p.h b/src/qml/qml/qqmlproperty_p.h index 544eab4c7f..bafcba5971 100644 --- a/src/qml/qml/qqmlproperty_p.h +++ b/src/qml/qml/qqmlproperty_p.h @@ -56,7 +56,8 @@ #include <private/qobject_p.h> #include <private/qtqmlglobal_p.h> -#include <private/qqmlpropertycache_p.h> +#include <private/qqmlrefcount_p.h> +#include <private/qqmlcontext_p.h> #include <private/qqmlboundsignalexpressionpointer_p.h> QT_BEGIN_NAMESPACE @@ -64,6 +65,7 @@ QT_BEGIN_NAMESPACE class QQmlContext; class QQmlEnginePrivate; class QQmlJavaScriptExpression; +class QQmlMetaObject; class Q_QML_PRIVATE_EXPORT QQmlPropertyPrivate : public QQmlRefCount { diff --git a/src/qml/qml/qqmlpropertycache.cpp b/src/qml/qml/qqmlpropertycache.cpp index 73bfd7bbaa..46457a8d76 100644 --- a/src/qml/qml/qqmlpropertycache.cpp +++ b/src/qml/qml/qqmlpropertycache.cpp @@ -46,6 +46,7 @@ #include <private/qmetaobject_p.h> #include <private/qmetaobjectbuilder_p.h> +#include <private/qqmlpropertycachemethodarguments_p.h> #include <private/qv4value_p.h> @@ -65,21 +66,6 @@ QT_BEGIN_NAMESPACE #define Q_INT16_MAX 32767 -class QQmlPropertyCacheMethodArguments -{ -public: - QQmlPropertyCacheMethodArguments *next; - - //for signal handler rewrites - QString *signalParameterStringForJS; - int parameterError:1; - int argumentsValid:1; - - QList<QByteArray> *names; - - int arguments[1]; -}; - // Flags that do *NOT* depend on the property's QMetaProperty::userType() and thus are quick // to load static QQmlPropertyData::Flags fastFlagsForProperty(const QMetaProperty &p) @@ -892,51 +878,6 @@ void QQmlPropertyData::markAsOverrideOf(QQmlPropertyData *predecessor) predecessor->_flags.isOverridden = true; } -struct StaticQtMetaObject : public QObject -{ - static const QMetaObject *get() - { return &staticQtMetaObject; } -}; - -static bool isNamedEnumeratorInScope(const QMetaObject *resolvedMetaObject, const QByteArray &scope, - const QByteArray &name) -{ - for (int i = resolvedMetaObject->enumeratorCount() - 1; i >= 0; --i) { - QMetaEnum m = resolvedMetaObject->enumerator(i); - if ((m.name() == name) && (scope.isEmpty() || (m.scope() == scope))) - return true; - } - return false; -} - -static bool isNamedEnumerator(const QMetaObject *metaObj, const QByteArray &scopedName) -{ - QByteArray scope; - QByteArray name; - int scopeIdx = scopedName.lastIndexOf("::"); - if (scopeIdx != -1) { - scope = scopedName.left(scopeIdx); - name = scopedName.mid(scopeIdx + 2); - } else { - name = scopedName; - } - - if (scope == "Qt") - return isNamedEnumeratorInScope(StaticQtMetaObject::get(), scope, name); - - if (isNamedEnumeratorInScope(metaObj, scope, name)) - return true; - - if (metaObj->d.relatedMetaObjects && !scope.isEmpty()) { - for (auto related = metaObj->d.relatedMetaObjects; *related; ++related) { - if (isNamedEnumeratorInScope(*related, scope, name)) - return true; - } - } - - return false; -} - QQmlPropertyCacheMethodArguments *QQmlPropertyCache::createArgumentsObject(int argc, const QList<QByteArray> &names) { typedef QQmlPropertyCacheMethodArguments A; @@ -1517,262 +1458,4 @@ QList<QByteArray> QQmlPropertyCache::signalParameterNames(int index) const return QList<QByteArray>(); } -// Returns true if \a from is assignable to a property of type \a to -bool QQmlMetaObject::canConvert(const QQmlMetaObject &from, const QQmlMetaObject &to) -{ - Q_ASSERT(!from.isNull() && !to.isNull()); - - struct I { static bool equal(const QMetaObject *lhs, const QMetaObject *rhs) { - return lhs == rhs || (lhs && rhs && lhs->d.stringdata == rhs->d.stringdata); - } }; - - const QMetaObject *tom = to._m.isT1()?to._m.asT1()->metaObject():to._m.asT2(); - if (tom == &QObject::staticMetaObject) return true; - - if (from._m.isT1() && to._m.isT1()) { // QQmlPropertyCache -> QQmlPropertyCache - QQmlPropertyCache *fromp = from._m.asT1(); - QQmlPropertyCache *top = to._m.asT1(); - - while (fromp) { - if (fromp == top) return true; - fromp = fromp->parent(); - } - } else if (from._m.isT1() && to._m.isT2()) { // QQmlPropertyCache -> QMetaObject - QQmlPropertyCache *fromp = from._m.asT1(); - - while (fromp) { - const QMetaObject *fromm = fromp->metaObject(); - if (fromm && I::equal(fromm, tom)) return true; - fromp = fromp->parent(); - } - } else if (from._m.isT2() && to._m.isT1()) { // QMetaObject -> QQmlPropertyCache - const QMetaObject *fromm = from._m.asT2(); - - if (!tom) return false; - - while (fromm) { - if (I::equal(fromm, tom)) return true; - fromm = fromm->superClass(); - } - } else { // QMetaObject -> QMetaObject - const QMetaObject *fromm = from._m.asT2(); - - while (fromm) { - if (I::equal(fromm, tom)) return true; - fromm = fromm->superClass(); - } - } - - return false; -} - -void QQmlMetaObject::resolveGadgetMethodOrPropertyIndex(QMetaObject::Call type, const QMetaObject **metaObject, int *index) -{ - int offset; - - switch (type) { - case QMetaObject::ReadProperty: - case QMetaObject::WriteProperty: - case QMetaObject::ResetProperty: - case QMetaObject::QueryPropertyDesignable: - case QMetaObject::QueryPropertyEditable: - case QMetaObject::QueryPropertyScriptable: - case QMetaObject::QueryPropertyStored: - case QMetaObject::QueryPropertyUser: - offset = (*metaObject)->propertyOffset(); - while (*index < offset) { - *metaObject = (*metaObject)->superClass(); - offset = (*metaObject)->propertyOffset(); - } - break; - case QMetaObject::InvokeMetaMethod: - offset = (*metaObject)->methodOffset(); - while (*index < offset) { - *metaObject = (*metaObject)->superClass(); - offset = (*metaObject)->methodOffset(); - } - break; - default: - offset = 0; - Q_UNIMPLEMENTED(); - offset = INT_MAX; - } - - *index -= offset; -} - -QQmlPropertyCache *QQmlMetaObject::propertyCache(QQmlEnginePrivate *e) const -{ - if (_m.isNull()) return nullptr; - if (_m.isT1()) return _m.asT1(); - else return e->cache(_m.asT2()); -} - -int QQmlMetaObject::methodReturnType(const QQmlPropertyData &data, QByteArray *unknownTypeError) const -{ - Q_ASSERT(!_m.isNull() && data.coreIndex() >= 0); - - int type = data.propType(); - - const char *propTypeName = nullptr; - - if (type == QMetaType::UnknownType) { - // Find the return type name from the method info - QMetaMethod m; - - if (_m.isT1()) { - QQmlPropertyCache *c = _m.asT1(); - Q_ASSERT(data.coreIndex() < c->methodIndexCacheStart + c->methodIndexCache.count()); - - while (data.coreIndex() < c->methodIndexCacheStart) - c = c->_parent; - - const QMetaObject *metaObject = c->createMetaObject(); - Q_ASSERT(metaObject); - m = metaObject->method(data.coreIndex()); - } else { - m = _m.asT2()->method(data.coreIndex()); - } - - type = m.returnType(); - propTypeName = m.typeName(); - } - - if (QMetaType::sizeOf(type) <= int(sizeof(int))) { - if (QMetaType::typeFlags(type) & QMetaType::IsEnumeration) - return QMetaType::Int; - - if (isNamedEnumerator(metaObject(), propTypeName)) - return QMetaType::Int; - - if (type == QMetaType::UnknownType) { - if (unknownTypeError) - *unknownTypeError = propTypeName; - } - } // else we know that it's a known type, as sizeOf(UnknownType) == 0 - - return type; -} - -int *QQmlMetaObject::methodParameterTypes(int index, ArgTypeStorage *argStorage, - QByteArray *unknownTypeError) const -{ - Q_ASSERT(!_m.isNull() && index >= 0); - - if (_m.isT1()) { - typedef QQmlPropertyCacheMethodArguments A; - - QQmlPropertyCache *c = _m.asT1(); - Q_ASSERT(index < c->methodIndexCacheStart + c->methodIndexCache.count()); - - while (index < c->methodIndexCacheStart) - c = c->_parent; - - QQmlPropertyData *rv = const_cast<QQmlPropertyData *>(&c->methodIndexCache.at(index - c->methodIndexCacheStart)); - - if (rv->arguments() && static_cast<A *>(rv->arguments())->argumentsValid) - return static_cast<A *>(rv->arguments())->arguments; - - const QMetaObject *metaObject = c->createMetaObject(); - Q_ASSERT(metaObject); - QMetaMethod m = metaObject->method(index); - - int argc = m.parameterCount(); - if (!rv->arguments()) { - A *args = c->createArgumentsObject(argc, m.parameterNames()); - rv->setArguments(args); - } - A *args = static_cast<A *>(rv->arguments()); - - QList<QByteArray> argTypeNames; // Only loaded if needed - - for (int ii = 0; ii < argc; ++ii) { - int type = m.parameterType(ii); - - if (QMetaType::sizeOf(type) > int(sizeof(int))) { - // Cannot be passed as int - // We know that it's a known type, as sizeOf(UnknownType) == 0 - } else if (QMetaType::typeFlags(type) & QMetaType::IsEnumeration) { - type = QMetaType::Int; - } else { - if (argTypeNames.isEmpty()) - argTypeNames = m.parameterTypes(); - if (isNamedEnumerator(metaObject, argTypeNames.at(ii))) { - type = QMetaType::Int; - } else if (type == QMetaType::UnknownType){ - if (unknownTypeError) - *unknownTypeError = argTypeNames.at(ii); - return nullptr; - } - - } - args->arguments[ii + 1] = type; - } - args->argumentsValid = true; - return static_cast<A *>(rv->arguments())->arguments; - - } else { - QMetaMethod m = _m.asT2()->method(index); - return methodParameterTypes(m, argStorage, unknownTypeError); - - } -} - -int *QQmlMetaObject::methodParameterTypes(const QMetaMethod &m, ArgTypeStorage *argStorage, - QByteArray *unknownTypeError) const -{ - Q_ASSERT(argStorage); - - int argc = m.parameterCount(); - argStorage->resize(argc + 1); - argStorage->operator[](0) = argc; - QList<QByteArray> argTypeNames; // Only loaded if needed - - for (int ii = 0; ii < argc; ++ii) { - int type = m.parameterType(ii); - if (QMetaType::sizeOf(type) > int(sizeof(int))) { - // Cannot be passed as int - // We know that it's a known type, as sizeOf(UnknownType) == 0 - } else if (QMetaType::typeFlags(type) & QMetaType::IsEnumeration) { - type = QMetaType::Int; - } else { - if (argTypeNames.isEmpty()) - argTypeNames = m.parameterTypes(); - if (isNamedEnumerator(_m.asT2(), argTypeNames.at(ii))) { - type = QMetaType::Int; - } else if (type == QMetaType::UnknownType) { - if (unknownTypeError) - *unknownTypeError = argTypeNames.at(ii); - return nullptr; - } - } - argStorage->operator[](ii + 1) = type; - } - - return argStorage->data(); -} - -void QQmlObjectOrGadget::metacall(QMetaObject::Call type, int index, void **argv) const -{ - if (ptr.isNull()) { - const QMetaObject *metaObject = _m.asT2(); - metaObject->d.static_metacall(nullptr, type, index, argv); - } - else if (ptr.isT1()) { - QMetaObject::metacall(ptr.asT1(), type, index, argv); - } - else { - const QMetaObject *metaObject = _m.asT1()->metaObject(); - QQmlMetaObject::resolveGadgetMethodOrPropertyIndex(type, &metaObject, &index); - metaObject->d.static_metacall(reinterpret_cast<QObject*>(ptr.asT2()), type, index, argv); - } -} - -int *QQmlStaticMetaObject::constructorParameterTypes(int index, ArgTypeStorage *dummy, - QByteArray *unknownTypeError) const -{ - QMetaMethod m = _m.asT2()->constructor(index); - return methodParameterTypes(m, dummy, unknownTypeError); -} - QT_END_NAMESPACE diff --git a/src/qml/qml/qqmlpropertycache_p.h b/src/qml/qml/qqmlpropertycache_p.h index c3c818eb77..4f47e5d351 100644 --- a/src/qml/qml/qqmlpropertycache_p.h +++ b/src/qml/qml/qqmlpropertycache_p.h @@ -57,339 +57,25 @@ #include "qqmlnotifier_p.h" #include <private/qqmlpropertyindex_p.h> -#include <private/qhashedstring_p.h> +#include <private/qlinkedstringhash_p.h> #include <QtCore/qvarlengtharray.h> #include <QtCore/qvector.h> #include <private/qv4value_p.h> +#include <private/qqmlpropertydata_p.h> +#include <private/qqmlenumdata_p.h> +#include <private/qqmlenumvalue_p.h> #include <limits> QT_BEGIN_NAMESPACE class QCryptographicHash; -class QMetaProperty; -class QQmlEngine; class QJSEngine; -class QQmlPropertyData; class QMetaObjectBuilder; -class QQmlPropertyCacheMethodArguments; class QQmlVMEMetaObject; -template <typename T> class QQmlPropertyCacheCreator; -template <typename T> class QQmlPropertyCacheAliasCreator; - -// 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 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 - V4HandleType = 7, // Property type is a QQmlV4Handle - 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 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; } - 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<qint16>::max()); - _propType = quint16(pt); - } - - int notifyIndex() const { return _notifyIndex; } - void setNotifyIndex(int idx) - { - Q_ASSERT(idx >= std::numeric_limits<qint16>::min()); - Q_ASSERT(idx <= std::numeric_limits<qint16>::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<qint16>::min()); - Q_ASSERT(idx <= std::numeric_limits<qint16>::max()); - _overrideIndex = qint16(idx); - } - - int coreIndex() const { return _coreIndex; } - void setCoreIndex(int idx) - { - Q_ASSERT(idx >= std::numeric_limits<qint16>::min()); - Q_ASSERT(idx <= std::numeric_limits<qint16>::max()); - _coreIndex = qint16(idx); - } - - quint8 revision() const { return _revision; } - void setRevision(quint8 rev) - { - Q_ASSERT(rev <= std::numeric_limits<quint8>::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<quint8>::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<qint16>::min()); - Q_ASSERT(off <= std::numeric_limits<qint16>::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 - -class QQmlPropertyData : public QQmlPropertyRawData -{ -public: - enum WriteFlag { - BypassInterceptor = 0x01, - DontRemoveBinding = 0x02, - RemoveBindingOnAliasWrite = 0x04 - }; - Q_DECLARE_FLAGS(WriteFlags, WriteFlag) - - inline QQmlPropertyData(); - inline QQmlPropertyData(const QQmlPropertyRawData &); - - inline bool operator==(const QQmlPropertyRawData &); - - static Flags flagsForProperty(const QMetaProperty &); - void load(const QMetaProperty &); - void load(const QMetaMethod &); - QString name(QObject *) const; - QString name(const QMetaObject *) const; - - void markAsOverrideOf(QQmlPropertyData *predecessor); - - inline void readProperty(QObject *target, void *property) const - { - void *args[] = { property, nullptr }; - readPropertyWithArgs(target, args); - } - - inline void readPropertyWithArgs(QObject *target, void *args[]) const - { - if (hasStaticMetaCallFunction()) - staticMetaCallFunction()(target, QMetaObject::ReadProperty, relativePropertyIndex(), args); - else if (isDirect()) - target->qt_metacall(QMetaObject::ReadProperty, coreIndex(), args); - else - QMetaObject::metacall(target, QMetaObject::ReadProperty, coreIndex(), args); - } - - bool writeProperty(QObject *target, void *value, WriteFlags flags) const - { - int status = -1; - void *argv[] = { value, nullptr, &status, &flags }; - if (flags.testFlag(BypassInterceptor) && hasStaticMetaCallFunction()) - staticMetaCallFunction()(target, QMetaObject::WriteProperty, relativePropertyIndex(), argv); - else if (flags.testFlag(BypassInterceptor) && isDirect()) - target->qt_metacall(QMetaObject::WriteProperty, coreIndex(), argv); - else - QMetaObject::metacall(target, QMetaObject::WriteProperty, coreIndex(), argv); - return true; - } - - static Flags defaultSignalFlags() - { - Flags f; - f.isSignal = true; - f.type = Flags::FunctionType; - f.isVMESignal = true; - return f; - } - - static Flags defaultSlotFlags() - { - Flags f; - f.type = Flags::FunctionType; - f.isVMEFunction = true; - return f; - } - -private: - friend class QQmlPropertyCache; - void lazyLoad(const QMetaProperty &); - void lazyLoad(const QMetaMethod &); - bool notFullyResolved() const { return _flags.notFullyResolved; } -}; - -struct QQmlEnumValue -{ - QQmlEnumValue() {} - QQmlEnumValue(const QString &n, int v) : namedValue(n), value(v) {} - QString namedValue; - int value = -1; -}; - -struct QQmlEnumData -{ - QString name; - QVector<QQmlEnumValue> values; -}; - class QQmlPropertyCacheMethodArguments; + class Q_QML_PRIVATE_EXPORT QQmlPropertyCache : public QQmlRefCount { public: @@ -399,8 +85,6 @@ public: void update(const QMetaObject *); void invalidate(const QMetaObject *); - // Used by qmlpuppet. Remove as soon Creator requires Qt 5.5. - void invalidate(void *, const QMetaObject *mo) { invalidate(mo); } QQmlPropertyCache *copy(); @@ -488,6 +172,10 @@ public: static bool addToHash(QCryptographicHash &hash, const QMetaObject &mo); QByteArray checksum(bool *ok); + + int allowedRevision(int index) const { return allowedRevisionCache[index]; } + void setAllowedRevision(int index, int allowed) { allowedRevisionCache[index] = allowed; } + private: friend class QQmlEnginePrivate; friend class QQmlCompiler; @@ -495,7 +183,6 @@ private: template <typename T> friend class QQmlPropertyCacheAliasCreator; friend class QQmlComponentAndAliasResolver; friend class QQmlMetaObject; - friend struct QQmlMetaTypeData; inline QQmlPropertyCache *copy(int reserve); @@ -507,7 +194,7 @@ private: QQmlPropertyCacheMethodArguments *createArgumentsObject(int count, const QList<QByteArray> &names); typedef QVector<QQmlPropertyData> IndexCache; - typedef QStringMultiHash<QPair<int, QQmlPropertyData *> > StringCache; + typedef QLinkedStringMultiHash<QPair<int, QQmlPropertyData *> > StringCache; typedef QVector<int> AllowedRevisionCache; QQmlPropertyData *findProperty(StringCache::ConstIterator it, QObject *, QQmlContextData *) const; @@ -558,170 +245,6 @@ private: typedef QQmlRefPointer<QQmlPropertyCache> QQmlPropertyCachePtr; -// QQmlMetaObject serves as a wrapper around either QMetaObject or QQmlPropertyCache. -// This is necessary as we delay creation of QMetaObject for synthesized QObjects, but -// we don't want to needlessly generate QQmlPropertyCaches every time we encounter a -// QObject type used in assignment or when we don't have a QQmlEngine etc. -// -// This class does NOT reference the propertycache. -class QQmlEnginePrivate; -class Q_QML_EXPORT QQmlMetaObject -{ -public: - typedef QVarLengthArray<int, 9> ArgTypeStorage; - - inline QQmlMetaObject(); - inline QQmlMetaObject(QObject *); - inline QQmlMetaObject(const QMetaObject *); - inline QQmlMetaObject(QQmlPropertyCache *); - inline QQmlMetaObject(const QQmlMetaObject &); - - inline QQmlMetaObject &operator=(const QQmlMetaObject &); - - inline bool isNull() const; - - inline const char *className() const; - inline int propertyCount() const; - - inline bool hasMetaObject() const; - inline const QMetaObject *metaObject() const; - - QQmlPropertyCache *propertyCache(QQmlEnginePrivate *) const; - - int methodReturnType(const QQmlPropertyData &data, QByteArray *unknownTypeError) const; - int *methodParameterTypes(int index, ArgTypeStorage *argStorage, - QByteArray *unknownTypeError) const; - - static bool canConvert(const QQmlMetaObject &from, const QQmlMetaObject &to); - - // static_metacall (on Gadgets) doesn't call the base implementation and therefore - // we need a helper to find the correct meta object and property/method index. - static void resolveGadgetMethodOrPropertyIndex(QMetaObject::Call type, const QMetaObject **metaObject, int *index); - -protected: - QBiPointer<QQmlPropertyCache, const QMetaObject> _m; - int *methodParameterTypes(const QMetaMethod &method, ArgTypeStorage *argStorage, - QByteArray *unknownTypeError) const; - -}; - -class QQmlObjectOrGadget: public QQmlMetaObject -{ -public: - QQmlObjectOrGadget(QObject *obj) - : QQmlMetaObject(obj), - ptr(obj) - {} - QQmlObjectOrGadget(QQmlPropertyCache *propertyCache, void *gadget) - : QQmlMetaObject(propertyCache) - , ptr(gadget) - {} - - void metacall(QMetaObject::Call type, int index, void **argv) const; - -private: - QBiPointer<QObject, void> ptr; - -protected: - QQmlObjectOrGadget(const QMetaObject* metaObject) - : QQmlMetaObject(metaObject) - {} - -}; - -class QQmlStaticMetaObject : public QQmlObjectOrGadget { -public: - QQmlStaticMetaObject(const QMetaObject* metaObject) - : QQmlObjectOrGadget(metaObject) - {} - int *constructorParameterTypes(int index, ArgTypeStorage *dummy, QByteArray *unknownTypeError) const; -}; - -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 V4HandleType: - case QVariantType: - type = from.type; - } -} - -QQmlPropertyData::QQmlPropertyData() -{ - setCoreIndex(-1); - setPropType(0); - setNotifyIndex(-1); - setOverrideIndex(-1); - setRevision(0); - setMetaObjectOffset(-1); - setArguments(nullptr); - trySetStaticMetaCallFunction(nullptr, 0); -} - -QQmlPropertyData::QQmlPropertyData(const QQmlPropertyRawData &d) -{ - *(static_cast<QQmlPropertyRawData *>(this)) = d; -} - -bool QQmlPropertyData::operator==(const QQmlPropertyRawData &other) -{ - return flags() == other.flags() && - propType() == other.propType() && - coreIndex() == other.coreIndex() && - notifyIndex() == other.notifyIndex() && - revision() == other.revision(); -} - inline QQmlPropertyData *QQmlPropertyCache::ensureResolved(QQmlPropertyData *p) const { if (p && Q_UNLIKELY(p->notFullyResolved())) @@ -880,124 +403,6 @@ bool QQmlPropertyCache::callJSFactoryMethod(QObject *object, void **args) const return false; } -QQmlMetaObject::QQmlMetaObject() -{ -} - -QQmlMetaObject::QQmlMetaObject(QObject *o) -{ - if (o) { - QQmlData *ddata = QQmlData::get(o, false); - if (ddata && ddata->propertyCache) _m = ddata->propertyCache; - else _m = o->metaObject(); - } -} - -QQmlMetaObject::QQmlMetaObject(const QMetaObject *m) -: _m(m) -{ -} - -QQmlMetaObject::QQmlMetaObject(QQmlPropertyCache *m) -: _m(m) -{ -} - -QQmlMetaObject::QQmlMetaObject(const QQmlMetaObject &o) -: _m(o._m) -{ -} - -QQmlMetaObject &QQmlMetaObject::operator=(const QQmlMetaObject &o) -{ - _m = o._m; - return *this; -} - -bool QQmlMetaObject::isNull() const -{ - return _m.isNull(); -} - -const char *QQmlMetaObject::className() const -{ - if (_m.isNull()) { - return nullptr; - } else if (_m.isT1()) { - return _m.asT1()->className(); - } else { - return _m.asT2()->className(); - } -} - -int QQmlMetaObject::propertyCount() const -{ - if (_m.isNull()) { - return 0; - } else if (_m.isT1()) { - return _m.asT1()->propertyCount(); - } else { - return _m.asT2()->propertyCount(); - } -} - -bool QQmlMetaObject::hasMetaObject() const -{ - return _m.isT2() || (!_m.isNull() && _m.asT1()->metaObject()); -} - -const QMetaObject *QQmlMetaObject::metaObject() const -{ - if (_m.isNull()) return nullptr; - if (_m.isT1()) return _m.asT1()->createMetaObject(); - else return _m.asT2(); -} - -class QQmlPropertyCacheVector -{ -public: - QQmlPropertyCacheVector() {} - QQmlPropertyCacheVector(QQmlPropertyCacheVector &&other) - : data(std::move(other.data)) {} - QQmlPropertyCacheVector &operator=(QQmlPropertyCacheVector &&other) { - QVector<QFlagPointer<QQmlPropertyCache>> moved(std::move(other.data)); - data.swap(moved); - return *this; - } - - ~QQmlPropertyCacheVector() { clear(); } - void resize(int size) { return data.resize(size); } - int count() const { return data.count(); } - void clear() - { - for (int i = 0; i < data.count(); ++i) { - if (QQmlPropertyCache *cache = data.at(i).data()) - cache->release(); - } - data.clear(); - } - - void append(QQmlPropertyCache *cache) { cache->addref(); data.append(cache); } - QQmlPropertyCache *at(int index) const { return data.at(index).data(); } - void set(int index, const QQmlRefPointer<QQmlPropertyCache> &replacement) { - if (QQmlPropertyCache *oldCache = data.at(index).data()) { - if (replacement.data() == oldCache) - return; - oldCache->release(); - } - data[index] = replacement.data(); - replacement->addref(); - } - - void setNeedsVMEMetaObject(int index) { data[index].setFlag(); } - bool needsVMEMetaObject(int index) const { return data.at(index).flag(); } -private: - Q_DISABLE_COPY(QQmlPropertyCacheVector) - QVector<QFlagPointer<QQmlPropertyCache>> data; -}; - -Q_DECLARE_OPERATORS_FOR_FLAGS(QQmlPropertyData::WriteFlags) - QT_END_NAMESPACE #endif // QQMLPROPERTYCACHE_P_H diff --git a/src/qml/qml/qqmlpropertycachemethodarguments_p.h b/src/qml/qml/qqmlpropertycachemethodarguments_p.h new file mode 100644 index 0000000000..62f09bdfff --- /dev/null +++ b/src/qml/qml/qqmlpropertycachemethodarguments_p.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** 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 QQMLPROPERTYCACHEMETODARGUMENTS_P_H +#define QQMLPROPERTYCACHEMETODARGUMENTS_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 <QtCore/qlist.h> +#include <QtCore/qbytearray.h> + +QT_BEGIN_NAMESPACE + +class QString; +class QQmlPropertyCacheMethodArguments +{ +public: + QQmlPropertyCacheMethodArguments *next; + + //for signal handler rewrites + QString *signalParameterStringForJS; + int parameterError:1; + int argumentsValid:1; + + QList<QByteArray> *names; + + int arguments[1]; +}; + +QT_END_NAMESPACE + +#endif // QQMLPROPERTYCACHEMETODARGUMENTS_P_H diff --git a/src/qml/qml/qqmlpropertycachevector_p.h b/src/qml/qml/qqmlpropertycachevector_p.h new file mode 100644 index 0000000000..1dff7c61a6 --- /dev/null +++ b/src/qml/qml/qqmlpropertycachevector_p.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** 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 QQMLPROPERTYCACHEVECTOR_P_H +#define QQMLPROPERTYCACHEVECTOR_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 <private/qflagpointer_p.h> +#include <private/qqmlpropertycache_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlPropertyCacheVector +{ +public: + QQmlPropertyCacheVector() {} + QQmlPropertyCacheVector(QQmlPropertyCacheVector &&other) + : data(std::move(other.data)) {} + QQmlPropertyCacheVector &operator=(QQmlPropertyCacheVector &&other) { + QVector<QFlagPointer<QQmlPropertyCache>> moved(std::move(other.data)); + data.swap(moved); + return *this; + } + + ~QQmlPropertyCacheVector() { clear(); } + void resize(int size) { return data.resize(size); } + int count() const { return data.count(); } + void clear() + { + for (int i = 0; i < data.count(); ++i) { + if (QQmlPropertyCache *cache = data.at(i).data()) + cache->release(); + } + data.clear(); + } + + void append(QQmlPropertyCache *cache) { cache->addref(); data.append(cache); } + QQmlPropertyCache *at(int index) const { return data.at(index).data(); } + void set(int index, const QQmlRefPointer<QQmlPropertyCache> &replacement) { + if (QQmlPropertyCache *oldCache = data.at(index).data()) { + if (replacement.data() == oldCache) + return; + oldCache->release(); + } + data[index] = replacement.data(); + replacement->addref(); + } + + void setNeedsVMEMetaObject(int index) { data[index].setFlag(); } + bool needsVMEMetaObject(int index) const { return data.at(index).flag(); } +private: + Q_DISABLE_COPY(QQmlPropertyCacheVector) + QVector<QFlagPointer<QQmlPropertyCache>> data; +}; + +QT_END_NAMESPACE + +#endif // QQMLPROPERTYCACHEVECTOR_P_H diff --git a/src/qml/qml/qqmlpropertydata_p.h b/src/qml/qml/qqmlpropertydata_p.h new file mode 100644 index 0000000000..a292bcc769 --- /dev/null +++ b/src/qml/qml/qqmlpropertydata_p.h @@ -0,0 +1,164 @@ +/**************************************************************************** +** +** 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 QQMLPROPERTYDATA_P_H +#define QQMLPROPERTYDATA_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 <private/qqmlpropertyrawdata_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlPropertyData : public QQmlPropertyRawData +{ +public: + enum WriteFlag { + BypassInterceptor = 0x01, + DontRemoveBinding = 0x02, + RemoveBindingOnAliasWrite = 0x04 + }; + Q_DECLARE_FLAGS(WriteFlags, WriteFlag) + + inline QQmlPropertyData(); + inline QQmlPropertyData(const QQmlPropertyRawData &); + + inline bool operator==(const QQmlPropertyRawData &); + + static Flags flagsForProperty(const QMetaProperty &); + void load(const QMetaProperty &); + void load(const QMetaMethod &); + QString name(QObject *) const; + QString name(const QMetaObject *) const; + + void markAsOverrideOf(QQmlPropertyData *predecessor); + + inline void readProperty(QObject *target, void *property) const + { + void *args[] = { property, nullptr }; + readPropertyWithArgs(target, args); + } + + inline void readPropertyWithArgs(QObject *target, void *args[]) const + { + if (hasStaticMetaCallFunction()) + staticMetaCallFunction()(target, QMetaObject::ReadProperty, relativePropertyIndex(), args); + else if (isDirect()) + target->qt_metacall(QMetaObject::ReadProperty, coreIndex(), args); + else + QMetaObject::metacall(target, QMetaObject::ReadProperty, coreIndex(), args); + } + + bool writeProperty(QObject *target, void *value, WriteFlags flags) const + { + int status = -1; + void *argv[] = { value, nullptr, &status, &flags }; + if (flags.testFlag(BypassInterceptor) && hasStaticMetaCallFunction()) + staticMetaCallFunction()(target, QMetaObject::WriteProperty, relativePropertyIndex(), argv); + else if (flags.testFlag(BypassInterceptor) && isDirect()) + target->qt_metacall(QMetaObject::WriteProperty, coreIndex(), argv); + else + QMetaObject::metacall(target, QMetaObject::WriteProperty, coreIndex(), argv); + return true; + } + + static Flags defaultSignalFlags() + { + Flags f; + f.isSignal = true; + f.type = Flags::FunctionType; + f.isVMESignal = true; + return f; + } + + static Flags defaultSlotFlags() + { + Flags f; + f.type = Flags::FunctionType; + f.isVMEFunction = true; + return f; + } + +private: + friend class QQmlPropertyCache; + void lazyLoad(const QMetaProperty &); + void lazyLoad(const QMetaMethod &); + bool notFullyResolved() const { return _flags.notFullyResolved; } +}; + +QQmlPropertyData::QQmlPropertyData() +{ + setCoreIndex(-1); + setPropType(0); + setNotifyIndex(-1); + setOverrideIndex(-1); + setRevision(0); + setMetaObjectOffset(-1); + setArguments(nullptr); + trySetStaticMetaCallFunction(nullptr, 0); +} + +QQmlPropertyData::QQmlPropertyData(const QQmlPropertyRawData &d) +{ + *(static_cast<QQmlPropertyRawData *>(this)) = d; +} + +bool QQmlPropertyData::operator==(const QQmlPropertyRawData &other) +{ + return flags() == other.flags() && + propType() == other.propType() && + coreIndex() == other.coreIndex() && + notifyIndex() == other.notifyIndex() && + revision() == other.revision(); +} + +Q_DECLARE_OPERATORS_FOR_FLAGS(QQmlPropertyData::WriteFlags) + +QT_END_NAMESPACE + +#endif // QQMLPROPERTYDATA_P_H diff --git a/src/qml/qml/qqmlpropertyrawdata_p.h b/src/qml/qml/qqmlpropertyrawdata_p.h new file mode 100644 index 0000000000..833c0f6ad0 --- /dev/null +++ b/src/qml/qml/qqmlpropertyrawdata_p.h @@ -0,0 +1,341 @@ +/**************************************************************************** +** +** 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 <private/qobject_p.h> + +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 + V4HandleType = 7, // Property type is a QQmlV4Handle + 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 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; } + 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<qint16>::max()); + _propType = quint16(pt); + } + + int notifyIndex() const { return _notifyIndex; } + void setNotifyIndex(int idx) + { + Q_ASSERT(idx >= std::numeric_limits<qint16>::min()); + Q_ASSERT(idx <= std::numeric_limits<qint16>::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<qint16>::min()); + Q_ASSERT(idx <= std::numeric_limits<qint16>::max()); + _overrideIndex = qint16(idx); + } + + int coreIndex() const { return _coreIndex; } + void setCoreIndex(int idx) + { + Q_ASSERT(idx >= std::numeric_limits<qint16>::min()); + Q_ASSERT(idx <= std::numeric_limits<qint16>::max()); + _coreIndex = qint16(idx); + } + + quint8 revision() const { return _revision; } + void setRevision(quint8 rev) + { + Q_ASSERT(rev <= std::numeric_limits<quint8>::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<quint8>::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<qint16>::min()); + Q_ASSERT(off <= std::numeric_limits<qint16>::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 V4HandleType: + case QVariantType: + type = from.type; + } +} + +QT_END_NAMESPACE + +#endif // QQMLPROPERTYRAWDATA_P_H diff --git a/src/qml/qml/qqmlstaticmetaobject.cpp b/src/qml/qml/qqmlstaticmetaobject.cpp new file mode 100644 index 0000000000..218d0134fd --- /dev/null +++ b/src/qml/qml/qqmlstaticmetaobject.cpp @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** 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 "qqmlstaticmetaobject_p.h" + +QT_BEGIN_NAMESPACE + +int *QQmlStaticMetaObject::constructorParameterTypes(int index, ArgTypeStorage *dummy, + QByteArray *unknownTypeError) const +{ + QMetaMethod m = _m.asT2()->constructor(index); + return methodParameterTypes(m, dummy, unknownTypeError); +} + +QT_END_NAMESPACE diff --git a/src/qml/qml/qqmlstaticmetaobject_p.h b/src/qml/qml/qqmlstaticmetaobject_p.h new file mode 100644 index 0000000000..e1ca496080 --- /dev/null +++ b/src/qml/qml/qqmlstaticmetaobject_p.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** 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 QQMLSTATICMETAOBJECT_P_H +#define QQMLSTATICMETAOBJECT_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 <private/qqmlobjectorgadget_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlStaticMetaObject : public QQmlObjectOrGadget { +public: + QQmlStaticMetaObject(const QMetaObject* metaObject) + : QQmlObjectOrGadget(metaObject) + {} + int *constructorParameterTypes(int index, ArgTypeStorage *dummy, QByteArray *unknownTypeError) const; +}; + +QT_END_NAMESPACE + +#endif // QQMLSTATICMETAOBJECT_P_H diff --git a/src/qml/qml/qqmltype.cpp b/src/qml/qml/qqmltype.cpp new file mode 100644 index 0000000000..df8078fb58 --- /dev/null +++ b/src/qml/qml/qqmltype.cpp @@ -0,0 +1,983 @@ +/**************************************************************************** +** +** 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 "qqmltype_p_p.h" + +#include <QtQml/qjsvalue.h> +#include <QtQml/qqmlengine.h> +#include <QtQml/qqmlcontext.h> +#include <QtQml/qqmlcomponent.h> + +#include <private/qqmlcustomparser_p.h> +#include <private/qqmldata_p.h> +#include <private/qqmlmetatypedata_p.h> +#include <private/qqmlpropertycache_p.h> + +QT_BEGIN_NAMESPACE + +void QQmlType::SingletonInstanceInfo::init(QQmlEngine *e) +{ + if (scriptCallback && scriptApi(e).isUndefined()) { + QJSValue value = scriptCallback(e, e); + if (value.isQObject()) { + QObject *o = value.toQObject(); + // even though the object is defined in C++, qmlContext(obj) and qmlEngine(obj) + // should behave identically to QML singleton types. + e->setContextForObject(o, new QQmlContext(e->rootContext(), e)); + } + setScriptApi(e, value); + } else if (qobjectCallback && !qobjectApi(e)) { + QObject *o = qobjectCallback(e, e); + setQObjectApi(e, o); + if (!o) { + qFatal("qmlRegisterSingletonType(): \"%s\" is not available because the callback function returns a null pointer.", qPrintable(typeName)); + } + // if this object can use a property cache, create it now + QQmlData::ensurePropertyCache(e, o); + // even though the object is defined in C++, qmlContext(obj) and qmlEngine(obj) + // should behave identically to QML singleton types. + e->setContextForObject(o, new QQmlContext(e->rootContext(), e)); + } else if (!url.isEmpty() && !qobjectApi(e)) { + QQmlComponent component(e, url, QQmlComponent::PreferSynchronous); + QObject *o = component.beginCreate(e->rootContext()); + setQObjectApi(e, o); + if (o) + component.completeCreate(); + } +} + +void QQmlType::SingletonInstanceInfo::destroy(QQmlEngine *e) +{ + // cleans up the engine-specific singleton instances if they exist. + scriptApis.remove(e); + QObject *o = qobjectApis.take(e); + if (o) { + QQmlData *ddata = QQmlData::get(o, false); + if (url.isEmpty() && ddata && ddata->indestructible && ddata->explicitIndestructibleSet) + return; + delete o; + } +} + +void QQmlType::SingletonInstanceInfo::setQObjectApi(QQmlEngine *e, QObject *o) +{ + qobjectApis.insert(e, o); +} + +QObject *QQmlType::SingletonInstanceInfo::qobjectApi(QQmlEngine *e) const +{ + return qobjectApis.value(e); +} + +void QQmlType::SingletonInstanceInfo::setScriptApi(QQmlEngine *e, const QJSValue &v) +{ + scriptApis.insert(e, v); +} + +QJSValue QQmlType::SingletonInstanceInfo::scriptApi(QQmlEngine *e) const +{ + return scriptApis.value(e); +} + +QQmlTypePrivate::QQmlTypePrivate(QQmlType::RegistrationType type) + : regType(type), iid(nullptr), typeId(0), listId(0), revision(0), + containsRevisionedAttributes(false), baseMetaObject(nullptr), + index(-1), isSetup(false), isEnumFromCacheSetup(false), isEnumFromBaseSetup(false), + haveSuperType(false) +{ + switch (type) { + case QQmlType::CppType: + extraData.cd = new QQmlCppTypeData; + extraData.cd->allocationSize = 0; + extraData.cd->newFunc = nullptr; + extraData.cd->parserStatusCast = -1; + extraData.cd->extFunc = nullptr; + extraData.cd->extMetaObject = nullptr; + extraData.cd->customParser = nullptr; + extraData.cd->attachedPropertiesFunc = nullptr; + extraData.cd->attachedPropertiesType = nullptr; + extraData.cd->propertyValueSourceCast = -1; + extraData.cd->propertyValueInterceptorCast = -1; + extraData.cd->registerEnumClassesUnscoped = true; + break; + case QQmlType::SingletonType: + case QQmlType::CompositeSingletonType: + extraData.sd = new QQmlSingletonTypeData; + extraData.sd->singletonInstanceInfo = nullptr; + break; + case QQmlType::InterfaceType: + extraData.cd = nullptr; + break; + case QQmlType::CompositeType: + extraData.fd = new QQmlCompositeTypeData; + break; + default: qFatal("QQmlTypePrivate Internal Error."); + } +} + +QQmlTypePrivate::~QQmlTypePrivate() +{ + qDeleteAll(scopedEnums); + switch (regType) { + case QQmlType::CppType: + // If attached properties were successfully registered, deregister them. + // (They may not have been registered if some other type used the same baseMetaObject) + if (extraData.cd->attachedPropertiesType) + QQmlMetaType::unregisterAttachedPropertyId(baseMetaObject, index); + delete extraData.cd->customParser; + delete extraData.cd; + break; + case QQmlType::SingletonType: + case QQmlType::CompositeSingletonType: + delete extraData.sd->singletonInstanceInfo; + delete extraData.sd; + break; + case QQmlType::CompositeType: + delete extraData.fd; + break; + default: //Also InterfaceType, because it has no extra data + break; + } +} + +QQmlType::QQmlType() = default; +QQmlType::QQmlType(const QQmlType &other) = default; +QQmlType::QQmlType(QQmlType &&other) = default; +QQmlType &QQmlType::operator =(const QQmlType &other) = default; +QQmlType &QQmlType::operator =(QQmlType &&other) = default; +QQmlType::QQmlType(const QQmlTypePrivate *priv) : d(priv) {} +QQmlType::~QQmlType() = default; + +QHashedString QQmlType::module() const +{ + if (!d) + return QHashedString(); + return d->module; +} + +int QQmlType::majorVersion() const +{ + if (!d) + return -1; + return d->version_maj; +} + +int QQmlType::minorVersion() const +{ + if (!d) + return -1; + return d->version_min; +} + +bool QQmlType::availableInVersion(int vmajor, int vminor) const +{ + Q_ASSERT(vmajor >= 0 && vminor >= 0); + if (!d) + return false; + return vmajor == d->version_maj && vminor >= d->version_min; +} + +bool QQmlType::availableInVersion(const QHashedStringRef &module, int vmajor, int vminor) const +{ + Q_ASSERT(vmajor >= 0 && vminor >= 0); + if (!d) + return false; + return module == d->module && vmajor == d->version_maj && vminor >= d->version_min; +} + +// returns the nearest _registered_ super class +QQmlType QQmlType::superType() const +{ + if (!d) + return QQmlType(); + if (!d->haveSuperType && d->baseMetaObject) { + const QMetaObject *mo = d->baseMetaObject->superClass(); + while (mo && !d->superType.isValid()) { + d->superType = QQmlMetaType::qmlType(mo, d->module, d->version_maj, d->version_min); + mo = mo->superClass(); + } + d->haveSuperType = true; + } + + return d->superType; +} + +QQmlType QQmlType::resolveCompositeBaseType(QQmlEnginePrivate *engine) const +{ + Q_ASSERT(isComposite()); + if (!engine || !d) + return QQmlType(); + QQmlRefPointer<QQmlTypeData> td(engine->typeLoader.getType(sourceUrl())); + if (td.isNull() || !td->isComplete()) + return QQmlType(); + QV4::CompiledData::CompilationUnit *compilationUnit = td->compilationUnit(); + const QMetaObject *mo = compilationUnit->rootPropertyCache()->firstCppMetaObject(); + return QQmlMetaType::qmlType(mo); +} + +QQmlPropertyCache *QQmlType::compositePropertyCache(QQmlEnginePrivate *engine) const +{ + // similar logic to resolveCompositeBaseType + Q_ASSERT(isComposite()); + if (!engine) + return nullptr; + QQmlRefPointer<QQmlTypeData> td(engine->typeLoader.getType(sourceUrl())); + if (td.isNull() || !td->isComplete()) + return nullptr; + QV4::CompiledData::CompilationUnit *compilationUnit = td->compilationUnit(); + return compilationUnit->rootPropertyCache().data(); +} + +static bool isPropertyRevisioned(const QMetaObject *mo, int index) +{ + int i = index; + i -= mo->propertyOffset(); + if (i < 0 && mo->d.superdata) + return isPropertyRevisioned(mo->d.superdata, index); + + const QMetaObjectPrivate *mop = reinterpret_cast<const QMetaObjectPrivate*>(mo->d.data); + if (i >= 0 && i < mop->propertyCount) { + int handle = mop->propertyData + 3*i; + int flags = mo->d.data[handle + 2]; + + return (flags & Revisioned); + } + + return false; +} + +void QQmlTypePrivate::init() const +{ + if (isSetup) + return; + + QMutexLocker lock(QQmlMetaType::typeRegistrationLock()); + if (isSetup) + return; + + const QMetaObject *mo = baseMetaObject; + if (!mo) { + // version 0 singleton type without metaobject information + return; + } + + if (regType == QQmlType::CppType) { + // Setup extended meta object + // XXX - very inefficient + if (extraData.cd->extFunc) { + QMetaObjectBuilder builder; + QQmlMetaType::clone(builder, extraData.cd->extMetaObject, extraData.cd->extMetaObject, + extraData.cd->extMetaObject); + builder.setFlags(QMetaObjectBuilder::DynamicMetaObject); + QMetaObject *mmo = builder.toMetaObject(); + mmo->d.superdata = mo; + QQmlProxyMetaObject::ProxyData data = { mmo, extraData.cd->extFunc, 0, 0 }; + metaObjects << data; + } + } + + metaObjects.append(QQmlMetaType::proxyData( + mo, baseMetaObject, metaObjects.isEmpty() ? nullptr + : metaObjects.constLast().metaObject)); + + for (int ii = 0; ii < metaObjects.count(); ++ii) { + metaObjects[ii].propertyOffset = + metaObjects.at(ii).metaObject->propertyOffset(); + metaObjects[ii].methodOffset = + metaObjects.at(ii).metaObject->methodOffset(); + } + + // Check for revisioned details + { + const QMetaObject *mo = nullptr; + if (metaObjects.isEmpty()) + mo = baseMetaObject; + else + mo = metaObjects.constFirst().metaObject; + + for (int ii = 0; !containsRevisionedAttributes && ii < mo->propertyCount(); ++ii) { + if (isPropertyRevisioned(mo, ii)) + containsRevisionedAttributes = true; + } + + for (int ii = 0; !containsRevisionedAttributes && ii < mo->methodCount(); ++ii) { + if (mo->method(ii).revision() != 0) + containsRevisionedAttributes = true; + } + } + + isSetup = true; + lock.unlock(); +} + +void QQmlTypePrivate::initEnums(const QQmlPropertyCache *cache) const +{ + if ((isEnumFromBaseSetup || !baseMetaObject) + && (isEnumFromCacheSetup || !cache)) { + return; + } + + init(); + + QMutexLocker lock(QQmlMetaType::typeRegistrationLock()); + + if (!isEnumFromCacheSetup && cache) { + insertEnumsFromPropertyCache(cache); + isEnumFromCacheSetup = true; + } + + if (!isEnumFromBaseSetup && baseMetaObject) { // could be singleton type without metaobject + insertEnums(baseMetaObject); + isEnumFromBaseSetup = true; + } +} + +void QQmlTypePrivate::insertEnums(const QMetaObject *metaObject) const +{ + // Add any enum values defined by 'related' classes + if (metaObject->d.relatedMetaObjects) { + const auto *related = metaObject->d.relatedMetaObjects; + if (related) { + while (*related) + insertEnums(*related++); + } + } + + QSet<QString> localEnums; + const QMetaObject *localMetaObject = nullptr; + + // Add any enum values defined by this class, overwriting any inherited values + for (int ii = 0; ii < metaObject->enumeratorCount(); ++ii) { + QMetaEnum e = metaObject->enumerator(ii); + const bool isScoped = e.isScoped(); + QStringHash<int> *scoped = isScoped ? new QStringHash<int>() : nullptr; + + // We allow enums in sub-classes to overwrite enums from base-classes, such as + // ListView.Center (from enum PositionMode) overwriting Item.Center (from enum TransformOrigin). + // This is acceptable because the _use_ of the enum from the QML side requires qualification + // anyway, i.e. ListView.Center vs. Item.Center. + // However if a class defines two enums with the same value, then that must produce a warning + // because it represents a valid conflict. + if (e.enclosingMetaObject() != localMetaObject) { + localEnums.clear(); + localMetaObject = e.enclosingMetaObject(); + } + + for (int jj = 0; jj < e.keyCount(); ++jj) { + const QString key = QString::fromUtf8(e.key(jj)); + const int value = e.value(jj); + if (!isScoped || (regType == QQmlType::CppType && extraData.cd->registerEnumClassesUnscoped)) { + if (localEnums.contains(key)) { + auto existingEntry = enums.find(key); + if (existingEntry != enums.end() && existingEntry.value() != value) { + qWarning("Previously registered enum will be overwritten due to name clash: %s.%s", metaObject->className(), key.toUtf8().constData()); + createEnumConflictReport(metaObject, key); + } + } else { + localEnums.insert(key); + } + enums.insert(key, value); + } + if (isScoped) + scoped->insert(key, value); + } + + if (isScoped) { + scopedEnums << scoped; + scopedEnumIndex.insert(QString::fromUtf8(e.name()), scopedEnums.count()-1); + } + } +} + +void QQmlTypePrivate::createListOfPossibleConflictingItems(const QMetaObject *metaObject, QList<EnumInfo> &enumInfoList, QStringList path) const +{ + path.append(QString::fromUtf8(metaObject->className())); + + if (metaObject->d.relatedMetaObjects) { + const auto *related = metaObject->d.relatedMetaObjects; + if (related) { + while (*related) + createListOfPossibleConflictingItems(*related++, enumInfoList, path); + } + } + + for (int ii = 0; ii < metaObject->enumeratorCount(); ++ii) { + const auto e = metaObject->enumerator(ii); + + for (int jj = 0; jj < e.keyCount(); ++jj) { + const QString key = QString::fromUtf8(e.key(jj)); + + EnumInfo enumInfo; + enumInfo.metaObjectName = QString::fromUtf8(metaObject->className()); + enumInfo.enumName = QString::fromUtf8(e.name()); + enumInfo.enumKey = key; + enumInfo.scoped = e.isScoped(); + enumInfo.path = path; + enumInfo.metaEnumScope = QString::fromUtf8(e.scope()); + enumInfoList.append(enumInfo); + } + } +} + +void QQmlTypePrivate::createEnumConflictReport(const QMetaObject *metaObject, const QString &conflictingKey) const +{ + QList<EnumInfo> enumInfoList; + + if (baseMetaObject) // prefer baseMetaObject if available + metaObject = baseMetaObject; + + if (!metaObject) { // If there is no metaObject at all return early + qWarning() << "No meta object information available. Skipping conflict analysis."; + return; + } + + createListOfPossibleConflictingItems(metaObject, enumInfoList, QStringList()); + + qWarning().noquote() << QLatin1String("Possible conflicting items:"); + // find items with conflicting key + for (const auto i : enumInfoList) { + if (i.enumKey == conflictingKey) + qWarning().noquote().nospace() << " " << i.metaObjectName << "." << i.enumName << "." << i.enumKey << " from scope " + << i.metaEnumScope << " injected by " << i.path.join(QLatin1String("->")); + } +} + +void QQmlTypePrivate::insertEnumsFromPropertyCache(const QQmlPropertyCache *cache) const +{ + const QMetaObject *cppMetaObject = cache->firstCppMetaObject(); + + while (cache && cache->metaObject() != cppMetaObject) { + + int count = cache->qmlEnumCount(); + for (int ii = 0; ii < count; ++ii) { + QStringHash<int> *scoped = new QStringHash<int>(); + QQmlEnumData *enumData = cache->qmlEnum(ii); + + for (int jj = 0; jj < enumData->values.count(); ++jj) { + const QQmlEnumValue &value = enumData->values.at(jj); + enums.insert(value.namedValue, value.value); + scoped->insert(value.namedValue, value.value); + } + scopedEnums << scoped; + scopedEnumIndex.insert(enumData->name, scopedEnums.count()-1); + } + cache = cache->parent(); + } + insertEnums(cppMetaObject); +} + +void QQmlTypePrivate::setName(const QString &uri, const QString &element) +{ + module = uri; + elementName = 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) { + if (d->regType == SingletonType || d->regType == CompositeSingletonType) + return d->extraData.sd->singletonInstanceInfo->typeName.toUtf8(); + else if (d->baseMetaObject) + return d->baseMetaObject->className(); + } + return QByteArray(); +} + +QString QQmlType::elementName() const +{ + if (!d) + return QString(); + return d->elementName; +} + +QString QQmlType::qmlTypeName() const +{ + if (!d) + return QString(); + return d->name; +} + +QObject *QQmlType::create() const +{ + if (!d || !isCreatable()) + return nullptr; + + d->init(); + + QObject *rv = (QObject *)operator new(d->extraData.cd->allocationSize); + d->extraData.cd->newFunc(rv); + + if (rv && !d->metaObjects.isEmpty()) + (void)new QQmlProxyMetaObject(rv, &d->metaObjects); + + return rv; +} + +void QQmlType::create(QObject **out, void **memory, size_t additionalMemory) const +{ + if (!d || !isCreatable()) + return; + + d->init(); + + QObject *rv = (QObject *)operator new(d->extraData.cd->allocationSize + additionalMemory); + d->extraData.cd->newFunc(rv); + + if (rv && !d->metaObjects.isEmpty()) + (void)new QQmlProxyMetaObject(rv, &d->metaObjects); + + *out = rv; + *memory = ((char *)rv) + d->extraData.cd->allocationSize; +} + +QQmlType::SingletonInstanceInfo *QQmlType::singletonInstanceInfo() const +{ + if (!d) + return nullptr; + if (d->regType != SingletonType && d->regType != CompositeSingletonType) + return nullptr; + return d->extraData.sd->singletonInstanceInfo; +} + +QQmlCustomParser *QQmlType::customParser() const +{ + if (!d) + return nullptr; + if (d->regType != CppType) + return nullptr; + return d->extraData.cd->customParser; +} + +QQmlType::CreateFunc QQmlType::createFunction() const +{ + if (!d || d->regType != CppType) + return nullptr; + return d->extraData.cd->newFunc; +} + +QString QQmlType::noCreationReason() const +{ + if (!d || d->regType != CppType) + return QString(); + return d->extraData.cd->noCreationReason; +} + +bool QQmlType::isCreatable() const +{ + return d && d->regType == CppType && d->extraData.cd->newFunc; +} + +QQmlType::ExtensionFunc QQmlType::extensionFunction() const +{ + if (!d || d->regType != CppType) + return nullptr; + return d->extraData.cd->extFunc; +} + +bool QQmlType::isExtendedType() const +{ + if (!d) + return false; + d->init(); + + return !d->metaObjects.isEmpty(); +} + +bool QQmlType::isSingleton() const +{ + return d && (d->regType == SingletonType || d->regType == CompositeSingletonType); +} + +bool QQmlType::isInterface() const +{ + return d && d->regType == InterfaceType; +} + +bool QQmlType::isComposite() const +{ + return d && (d->regType == CompositeType || d->regType == CompositeSingletonType); +} + +bool QQmlType::isCompositeSingleton() const +{ + return d && d->regType == CompositeSingletonType; +} + +int QQmlType::typeId() const +{ + return d ? d->typeId : -1; +} + +int QQmlType::qListTypeId() const +{ + return d ? d->listId : -1; +} + +const QMetaObject *QQmlType::metaObject() const +{ + if (!d) + return nullptr; + d->init(); + + if (d->metaObjects.isEmpty()) + return d->baseMetaObject; + else + return d->metaObjects.constFirst().metaObject; + +} + +const QMetaObject *QQmlType::baseMetaObject() const +{ + return d ? d->baseMetaObject : nullptr; +} + +bool QQmlType::containsRevisionedAttributes() const +{ + if (!d) + return false; + d->init(); + + return d->containsRevisionedAttributes; +} + +int QQmlType::metaObjectRevision() const +{ + return d ? d->revision : -1; +} + +QQmlAttachedPropertiesFunc QQmlType::attachedPropertiesFunction(QQmlEnginePrivate *engine) const +{ + if (!d) + return nullptr; + if (d->regType == CppType) + return d->extraData.cd->attachedPropertiesFunc; + + QQmlType base; + if (d->regType == CompositeType) + base = resolveCompositeBaseType(engine); + return base.attachedPropertiesFunction(engine); +} + +const QMetaObject *QQmlType::attachedPropertiesType(QQmlEnginePrivate *engine) const +{ + if (!d) + return nullptr; + if (d->regType == CppType) + return d->extraData.cd->attachedPropertiesType; + + QQmlType base; + if (d->regType == CompositeType) + base = resolveCompositeBaseType(engine); + return base.attachedPropertiesType(engine); +} + +/* +This is the id passed to qmlAttachedPropertiesById(). This is different from the index +for the case that a single class is registered under two or more names (eg. Item in +Qt 4.7 and QtQuick 1.0). +*/ +int QQmlType::attachedPropertiesId(QQmlEnginePrivate *engine) const +{ + if (!d) + return -1; + if (d->regType == CppType) + return d->extraData.cd->attachedPropertiesId; + + QQmlType base; + if (d->regType == CompositeType) + base = resolveCompositeBaseType(engine); + return base.attachedPropertiesId(engine); +} + +int QQmlType::parserStatusCast() const +{ + if (!d || d->regType != CppType) + return -1; + return d->extraData.cd->parserStatusCast; +} + +int QQmlType::propertyValueSourceCast() const +{ + if (!d || d->regType != CppType) + return -1; + return d->extraData.cd->propertyValueSourceCast; +} + +int QQmlType::propertyValueInterceptorCast() const +{ + if (!d || d->regType != CppType) + return -1; + return d->extraData.cd->propertyValueInterceptorCast; +} + +const char *QQmlType::interfaceIId() const +{ + if (!d || d->regType != InterfaceType) + return nullptr; + return d->iid; +} + +int QQmlType::index() const +{ + return d ? d->index : -1; +} + +QUrl QQmlType::sourceUrl() const +{ + if (d) { + if (d->regType == CompositeType) + return d->extraData.fd->url; + else if (d->regType == CompositeSingletonType) + return d->extraData.sd->singletonInstanceInfo->url; + } + return QUrl(); +} + +int QQmlType::enumValue(QQmlEnginePrivate *engine, const QHashedStringRef &name, bool *ok) const +{ + Q_ASSERT(ok); + if (d) { + const QQmlPropertyCache *cache = isComposite() ? compositePropertyCache(engine) : nullptr; + + *ok = true; + + d->initEnums(cache); + + int *rv = d->enums.value(name); + if (rv) + return *rv; + } + + *ok = false; + return -1; +} + +int QQmlType::enumValue(QQmlEnginePrivate *engine, const QHashedCStringRef &name, bool *ok) const +{ + Q_ASSERT(ok); + if (d) { + const QQmlPropertyCache *cache = isComposite() ? compositePropertyCache(engine) : nullptr; + + *ok = true; + + d->initEnums(cache); + + int *rv = d->enums.value(name); + if (rv) + return *rv; + } + + *ok = false; + return -1; +} + +int QQmlType::enumValue(QQmlEnginePrivate *engine, const QV4::String *name, bool *ok) const +{ + Q_ASSERT(ok); + if (d) { + const QQmlPropertyCache *cache = isComposite() ? compositePropertyCache(engine) : nullptr; + *ok = true; + + d->initEnums(cache); + + int *rv = d->enums.value(name); + if (rv) + return *rv; + } + + *ok = false; + return -1; +} + +int QQmlType::scopedEnumIndex(QQmlEnginePrivate *engine, const QV4::String *name, bool *ok) const +{ + Q_ASSERT(ok); + if (d) { + const QQmlPropertyCache *cache = isComposite() ? compositePropertyCache(engine) : nullptr; + *ok = true; + + d->initEnums(cache); + + int *rv = d->scopedEnumIndex.value(name); + if (rv) + return *rv; + } + + *ok = false; + return -1; +} + +int QQmlType::scopedEnumIndex(QQmlEnginePrivate *engine, const QString &name, bool *ok) const +{ + Q_ASSERT(ok); + if (d) { + const QQmlPropertyCache *cache = isComposite() ? compositePropertyCache(engine) : nullptr; + *ok = true; + + d->initEnums(cache); + + int *rv = d->scopedEnumIndex.value(name); + if (rv) + return *rv; + } + + *ok = false; + return -1; +} + +int QQmlType::scopedEnumValue(QQmlEnginePrivate *engine, int index, const QV4::String *name, bool *ok) const +{ + Q_UNUSED(engine) + Q_ASSERT(ok); + *ok = true; + + if (d) { + Q_ASSERT(index > -1 && index < d->scopedEnums.count()); + int *rv = d->scopedEnums.at(index)->value(name); + if (rv) + return *rv; + } + + *ok = false; + return -1; +} + +int QQmlType::scopedEnumValue(QQmlEnginePrivate *engine, int index, const QString &name, bool *ok) const +{ + Q_UNUSED(engine) + Q_ASSERT(ok); + *ok = true; + + if (d) { + Q_ASSERT(index > -1 && index < d->scopedEnums.count()); + int *rv = d->scopedEnums.at(index)->value(name); + if (rv) + return *rv; + } + + *ok = false; + return -1; +} + +int QQmlType::scopedEnumValue(QQmlEnginePrivate *engine, const QByteArray &scopedEnumName, const QByteArray &name, bool *ok) const +{ + Q_ASSERT(ok); + if (d) { + const QQmlPropertyCache *cache = isComposite() ? compositePropertyCache(engine) : nullptr; + *ok = true; + + d->initEnums(cache); + + int *rv = d->scopedEnumIndex.value(QHashedCStringRef(scopedEnumName.constData(), scopedEnumName.length())); + if (rv) { + int index = *rv; + Q_ASSERT(index > -1 && index < d->scopedEnums.count()); + rv = d->scopedEnums.at(index)->value(QHashedCStringRef(name.constData(), name.length())); + if (rv) + return *rv; + } + } + + *ok = false; + return -1; +} + +int QQmlType::scopedEnumValue(QQmlEnginePrivate *engine, const QStringRef &scopedEnumName, const QStringRef &name, bool *ok) const +{ + Q_ASSERT(ok); + if (d) { + const QQmlPropertyCache *cache = isComposite() ? compositePropertyCache(engine) : nullptr; + *ok = true; + + d->initEnums(cache); + + int *rv = d->scopedEnumIndex.value(QHashedStringRef(scopedEnumName)); + if (rv) { + int index = *rv; + Q_ASSERT(index > -1 && index < d->scopedEnums.count()); + rv = d->scopedEnums.at(index)->value(QHashedStringRef(name)); + if (rv) + return *rv; + } + } + + *ok = false; + return -1; +} + +void QQmlType::refHandle(const QQmlTypePrivate *priv) +{ + if (priv) + priv->addref(); +} + +void QQmlType::derefHandle(const QQmlTypePrivate *priv) +{ + if (priv) + priv->release(); +} + +int QQmlType::refCount(const QQmlTypePrivate *priv) +{ + if (priv) + return priv->count(); + return -1; +} + +QT_END_NAMESPACE diff --git a/src/qml/qml/qqmltype_p.h b/src/qml/qml/qqmltype_p.h new file mode 100644 index 0000000000..1d1345a0cb --- /dev/null +++ b/src/qml/qml/qqmltype_p.h @@ -0,0 +1,211 @@ +/**************************************************************************** +** +** 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 QQMLTYPE_P_H +#define QQMLTYPE_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 <private/qtqmlglobal_p.h> +#include <private/qqmlrefcount_p.h> + +#include <QtQml/qqmlprivate.h> +#include <QtQml/qjsvalue.h> + +#include <QtCore/qobject.h> + +QT_BEGIN_NAMESPACE + +class QHashedCStringRef; +class QQmlTypePrivate; +class QHashedString; +class QHashedStringRef; +class QQmlCustomParser; +class QQmlEnginePrivate; +class QQmlPropertyCache; + +namespace QV4 { +struct String; +} + +class Q_QML_PRIVATE_EXPORT QQmlType +{ +public: + QQmlType(); + QQmlType(const QQmlType &other); + QQmlType(QQmlType &&other); + QQmlType &operator =(const QQmlType &other); + QQmlType &operator =(QQmlType &&other); + explicit QQmlType(const QQmlTypePrivate *priv); + ~QQmlType(); + + bool operator ==(const QQmlType &other) const { + return d.data() == other.d.data(); + } + + bool isValid() const { return !d.isNull(); } + const QQmlTypePrivate *key() const { return d.data(); } + + QByteArray typeName() const; + QString qmlTypeName() const; + QString elementName() const; + + QHashedString module() const; + int majorVersion() const; + int minorVersion() const; + + bool availableInVersion(int vmajor, int vminor) const; + bool availableInVersion(const QHashedStringRef &module, int vmajor, int vminor) const; + + QObject *create() const; + void create(QObject **, void **, size_t) const; + + typedef void (*CreateFunc)(void *); + CreateFunc createFunction() const; + QQmlCustomParser *customParser() const; + + bool isCreatable() const; + typedef QObject *(*ExtensionFunc)(QObject *); + ExtensionFunc extensionFunction() const; + bool isExtendedType() const; + QString noCreationReason() const; + + bool isSingleton() const; + bool isInterface() const; + bool isComposite() const; + bool isCompositeSingleton() const; + + int typeId() const; + int qListTypeId() const; + + const QMetaObject *metaObject() const; + const QMetaObject *baseMetaObject() const; + int metaObjectRevision() const; + bool containsRevisionedAttributes() const; + + QQmlAttachedPropertiesFunc attachedPropertiesFunction(QQmlEnginePrivate *engine) const; + const QMetaObject *attachedPropertiesType(QQmlEnginePrivate *engine) const; + int attachedPropertiesId(QQmlEnginePrivate *engine) const; + + int parserStatusCast() const; + const char *interfaceIId() const; + int propertyValueSourceCast() const; + int propertyValueInterceptorCast() const; + + int index() const; + + class Q_QML_PRIVATE_EXPORT SingletonInstanceInfo + { + public: + SingletonInstanceInfo() + : scriptCallback(nullptr), qobjectCallback(nullptr), instanceMetaObject(nullptr) {} + + QJSValue (*scriptCallback)(QQmlEngine *, QJSEngine *); + QObject *(*qobjectCallback)(QQmlEngine *, QJSEngine *); + const QMetaObject *instanceMetaObject; + QString typeName; + QUrl url; // used by composite singletons + + void setQObjectApi(QQmlEngine *, QObject *); + QObject *qobjectApi(QQmlEngine *) const; + void setScriptApi(QQmlEngine *, const QJSValue &); + QJSValue scriptApi(QQmlEngine *) const; + + void init(QQmlEngine *); + void destroy(QQmlEngine *); + + QHash<QQmlEngine *, QJSValue> scriptApis; + QHash<QQmlEngine *, QObject *> qobjectApis; + }; + SingletonInstanceInfo *singletonInstanceInfo() const; + + QUrl sourceUrl() const; + + int enumValue(QQmlEnginePrivate *engine, const QHashedStringRef &, bool *ok) const; + int enumValue(QQmlEnginePrivate *engine, const QHashedCStringRef &, bool *ok) const; + int enumValue(QQmlEnginePrivate *engine, const QV4::String *, bool *ok) const; + + int scopedEnumIndex(QQmlEnginePrivate *engine, const QV4::String *, bool *ok) const; + int scopedEnumIndex(QQmlEnginePrivate *engine, const QString &, bool *ok) const; + int scopedEnumValue(QQmlEnginePrivate *engine, int index, const QV4::String *, bool *ok) const; + int scopedEnumValue(QQmlEnginePrivate *engine, int index, const QString &, bool *ok) const; + int scopedEnumValue(QQmlEnginePrivate *engine, const QByteArray &, const QByteArray &, bool *ok) const; + int scopedEnumValue(QQmlEnginePrivate *engine, const QStringRef &, const QStringRef &, bool *ok) const; + + const QQmlTypePrivate *priv() const { return d.data(); } + static void refHandle(const QQmlTypePrivate *priv); + static void derefHandle(const QQmlTypePrivate *priv); + static int refCount(const QQmlTypePrivate *priv); + + enum RegistrationType { + CppType = 0, + SingletonType = 1, + InterfaceType = 2, + CompositeType = 3, + CompositeSingletonType = 4, + AnyRegistrationType = 255 + }; + +private: + QQmlType superType() const; + QQmlType resolveCompositeBaseType(QQmlEnginePrivate *engine) const; + int resolveCompositeEnumValue(QQmlEnginePrivate *engine, const QString &name, bool *ok) const; + QQmlPropertyCache *compositePropertyCache(QQmlEnginePrivate *engine) const; + friend uint qHash(const QQmlType &t, uint seed); + + QQmlRefPointer<const QQmlTypePrivate> d; +}; + +inline uint qHash(const QQmlType &t, uint seed = 0) +{ + return qHash(reinterpret_cast<quintptr>(t.d.data()), seed); +} + +QT_END_NAMESPACE + +#endif // QQMLTYPE_P_H diff --git a/src/qml/qml/qqmltype_p_p.h b/src/qml/qml/qqmltype_p_p.h new file mode 100644 index 0000000000..dd6e046129 --- /dev/null +++ b/src/qml/qml/qqmltype_p_p.h @@ -0,0 +1,162 @@ +/**************************************************************************** +** +** 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 QQMLTYPE_P_P_H +#define QQMLTYPE_P_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 <private/qqmltype_p.h> +#include <private/qstringhash_p.h> +#include <private/qqmlproxymetaobject_p.h> +#include <private/qqmlrefcount_p.h> +#include <private/qqmlpropertycache_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlTypePrivate : public QQmlRefCount +{ + Q_DISABLE_COPY_MOVE(QQmlTypePrivate) +public: + QQmlTypePrivate(QQmlType::RegistrationType type); + + void init() const; + void initEnums(const QQmlPropertyCache *cache = nullptr) const; + void insertEnums(const QMetaObject *metaObject) const; + void insertEnumsFromPropertyCache(const QQmlPropertyCache *cache) const; + + QQmlType::RegistrationType regType; + + struct QQmlCppTypeData + { + int allocationSize; + void (*newFunc)(void *); + QString noCreationReason; + int parserStatusCast; + QObject *(*extFunc)(QObject *); + const QMetaObject *extMetaObject; + QQmlCustomParser *customParser; + QQmlAttachedPropertiesFunc attachedPropertiesFunc; + const QMetaObject *attachedPropertiesType; + int attachedPropertiesId; + int propertyValueSourceCast; + int propertyValueInterceptorCast; + bool registerEnumClassesUnscoped; + }; + + struct QQmlSingletonTypeData + { + QQmlType::SingletonInstanceInfo *singletonInstanceInfo; + }; + + struct QQmlCompositeTypeData + { + QUrl url; + }; + + union extraData { + QQmlCppTypeData* cd; + QQmlSingletonTypeData* sd; + QQmlCompositeTypeData* fd; + } extraData; + + const char *iid; + QHashedString module; + QString name; + QString elementName; + int version_maj; + int version_min; + int typeId; + int listId; + int revision; + mutable bool containsRevisionedAttributes; + mutable QQmlType superType; + const QMetaObject *baseMetaObject; + + int index; + mutable volatile bool isSetup:1; + mutable volatile bool isEnumFromCacheSetup:1; + mutable volatile bool isEnumFromBaseSetup:1; + mutable bool haveSuperType:1; + mutable QList<QQmlProxyMetaObject::ProxyData> metaObjects; + mutable QStringHash<int> enums; + mutable QStringHash<int> scopedEnumIndex; // maps from enum name to index in scopedEnums + mutable QList<QStringHash<int>*> scopedEnums; + + struct PropertyCacheByMinorVersion + { + PropertyCacheByMinorVersion() : cache(nullptr), minorVersion(-1) {} + explicit PropertyCacheByMinorVersion(QQmlPropertyCache *pc, int ver) : cache(pc), minorVersion(ver) {} + QQmlPropertyCachePtr cache; + int minorVersion; + }; + QVector<PropertyCacheByMinorVersion> propertyCaches; + QQmlPropertyCache *propertyCacheForMinorVersion(int minorVersion) const; + void setPropertyCacheForMinorVersion(int minorVersion, QQmlPropertyCache *cache); + + void setName(const QString &uri, const QString &element); + +private: + ~QQmlTypePrivate() override; + + struct EnumInfo { + QStringList path; + QString metaObjectName; + QString enumName; + QString enumKey; + QString metaEnumScope; + bool scoped; + }; + + void createListOfPossibleConflictingItems(const QMetaObject *metaObject, QList<EnumInfo> &enumInfoList, QStringList path) const; + void createEnumConflictReport(const QMetaObject *metaObject, const QString &conflictingKey) const; +}; + +QT_END_NAMESPACE + +#endif // QQMLTYPE_P_P_H diff --git a/src/qml/qml/qqmltypemodule.cpp b/src/qml/qml/qqmltypemodule.cpp new file mode 100644 index 0000000000..4d7553fbab --- /dev/null +++ b/src/qml/qml/qqmltypemodule.cpp @@ -0,0 +1,174 @@ +/**************************************************************************** +** +** 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 "qqmltypemodule_p_p.h" + +#include <private/qqmltype_p_p.h> + +#include <QtCore/qmutex.h> + +QT_BEGIN_NAMESPACE + +QQmlTypeModule::QQmlTypeModule(const QString &module, int majorVersion) + : d(new QQmlTypeModulePrivate(module, majorVersion)) +{ +} + +QQmlTypeModule::~QQmlTypeModule() +{ + delete d; +} + +QString QQmlTypeModule::module() const +{ + // No need to lock. d->module is const + return d->module; +} + +int QQmlTypeModule::majorVersion() const +{ + // No need to lock. d->majorVersion is const + return d->majorVersion; +} + +int QQmlTypeModule::minimumMinorVersion() const +{ + return d->minMinorVersion.load(); +} + +int QQmlTypeModule::maximumMinorVersion() const +{ + return d->maxMinorVersion.load(); +} + +void QQmlTypeModule::addMinorVersion(int version) +{ + for (int oldVersion = d->minMinorVersion.load(); + oldVersion > version && !d->minMinorVersion.testAndSetOrdered(oldVersion, version); + oldVersion = d->minMinorVersion.load()) { + } + + for (int oldVersion = d->maxMinorVersion.load(); + oldVersion < version && !d->maxMinorVersion.testAndSetOrdered(oldVersion, version); + oldVersion = d->maxMinorVersion.load()) { + } +} + +void QQmlTypeModule::add(QQmlTypePrivate *type) +{ + QMutexLocker lock(&d->mutex); + addMinorVersion(type->version_min); + + QList<QQmlTypePrivate *> &list = d->typeHash[type->elementName]; + for (int ii = 0; ii < list.count(); ++ii) { + Q_ASSERT(list.at(ii)); + if (list.at(ii)->version_min < type->version_min) { + list.insert(ii, type); + return; + } + } + list.append(type); +} + +void QQmlTypeModule::remove(const QQmlTypePrivate *type) +{ + QMutexLocker lock(&d->mutex); + for (auto elementIt = d->typeHash.begin(); elementIt != d->typeHash.end();) { + QQmlMetaType::removeQQmlTypePrivate(elementIt.value(), type); + +#if 0 + if (list.isEmpty()) + elementIt = typeHash.erase(elementIt); + else + ++elementIt; +#else + ++elementIt; +#endif + } +} + +bool QQmlTypeModule::isLocked() const +{ + return d->locked.load() != 0; +} + +void QQmlTypeModule::lock() +{ + d->locked.store(1); +} + +QQmlType QQmlTypeModule::type(const QHashedStringRef &name, int minor) const +{ + QMutexLocker lock(&d->mutex); + QList<QQmlTypePrivate *> *types = d->typeHash.value(name); + if (types) { + for (int ii = 0; ii < types->count(); ++ii) + if (types->at(ii)->version_min <= minor) + return QQmlType(types->at(ii)); + } + + return QQmlType(); +} + +QQmlType QQmlTypeModule::type(const QV4::String *name, int minor) const +{ + QMutexLocker lock(&d->mutex); + QList<QQmlTypePrivate *> *types = d->typeHash.value(name); + if (types) { + for (int ii = 0; ii < types->count(); ++ii) + if (types->at(ii)->version_min <= minor) + return QQmlType(types->at(ii)); + } + + return QQmlType(); +} + +void QQmlTypeModule::walkCompositeSingletons(const std::function<void(const QQmlType &)> &callback) const +{ + QMutexLocker lock(&d->mutex); + for (auto typeCandidates = d->typeHash.begin(), end = d->typeHash.end(); + typeCandidates != end; ++typeCandidates) { + for (auto type: typeCandidates.value()) { + if (type->regType == QQmlType::CompositeSingletonType) + callback(QQmlType(type)); + } + } +} + +QT_END_NAMESPACE diff --git a/src/qml/qml/qqmltypemodule_p.h b/src/qml/qml/qqmltypemodule_p.h new file mode 100644 index 0000000000..b84a91b5db --- /dev/null +++ b/src/qml/qml/qqmltypemodule_p.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** 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 QQMLTYPEMODULE_P_H +#define QQMLTYPEMODULE_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 <QtQml/qtqmlglobal.h> +#include <QtCore/qstring.h> + +#include <functional> + +QT_BEGIN_NAMESPACE + +class QQmlType; +class QQmlTypePrivate; +struct QQmlMetaTypeData; +class QHashedString; +class QHashedStringRef; + +namespace QV4 { +struct String; +} + +class QQmlTypeModulePrivate; +class QQmlTypeModule +{ +public: + QQmlTypeModule(const QString &uri = QString(), int majorVersion = 0); + ~QQmlTypeModule(); + + void add(QQmlTypePrivate *); + void remove(const QQmlTypePrivate *type); + + bool isLocked() const; + void lock(); + + QString module() const; + int majorVersion() const; + + void addMinorVersion(int minorVersion); + int minimumMinorVersion() const; + int maximumMinorVersion() const; + + QQmlType type(const QHashedStringRef &, int) const; + QQmlType type(const QV4::String *, int) const; + + void walkCompositeSingletons(const std::function<void(const QQmlType &)> &callback) const; + +private: + QQmlTypeModulePrivate *d; +}; + +QT_END_NAMESPACE + +#endif // QQMLTYPEMODULE_P_H diff --git a/src/qml/qml/qqmltypemodule_p_p.h b/src/qml/qml/qqmltypemodule_p_p.h new file mode 100644 index 0000000000..b1dab1c4a0 --- /dev/null +++ b/src/qml/qml/qqmltypemodule_p_p.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** 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 QQMLTYPEMODULE_P_P_H +#define QQMLTYPEMODULE_P_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 <private/qqmltypemodule_p.h> +#include <private/qstringhash_p.h> +#include <private/qqmlmetatypedata_p.h> + +#include <QtCore/qmutex.h> + +QT_BEGIN_NAMESPACE + +class QQmlTypeModulePrivate +{ +public: + QQmlTypeModulePrivate(QString module, int majorVersion) : + module(std::move(module)), majorVersion(majorVersion) + {} + + const QString module; + const int majorVersion = 0; + + // Can only ever decrease + QAtomicInt minMinorVersion = std::numeric_limits<int>::max(); + + // Can only ever increase + QAtomicInt maxMinorVersion = 0; + + // Bool. Can only be set to 1 once. + QAtomicInt locked = 0; + + typedef QStringHash<QList<QQmlTypePrivate *> > TypeHash; + TypeHash typeHash; + + QMutex mutex; +}; + +QT_END_NAMESPACE + +#endif // QQMLTYPEMODULE_P_P_H diff --git a/src/qml/qml/qqmltypemoduleversion.cpp b/src/qml/qml/qqmltypemoduleversion.cpp new file mode 100644 index 0000000000..bbbfa1a7b6 --- /dev/null +++ b/src/qml/qml/qqmltypemoduleversion.cpp @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** 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 "qqmltypemoduleversion_p.h" + +#include <private/qqmltype_p.h> +#include <private/qqmltypemodule_p.h> + +QT_BEGIN_NAMESPACE + +QQmlTypeModuleVersion::QQmlTypeModuleVersion() + : m_module(nullptr), m_minor(0) +{ +} + +QQmlTypeModuleVersion::QQmlTypeModuleVersion(QQmlTypeModule *module, int minor) + : m_module(module), m_minor(minor) +{ + Q_ASSERT(m_module); + Q_ASSERT(m_minor >= 0); +} + +QQmlTypeModuleVersion::QQmlTypeModuleVersion(const QQmlTypeModuleVersion &o) + : m_module(o.m_module), m_minor(o.m_minor) +{ +} + +QQmlTypeModuleVersion &QQmlTypeModuleVersion::operator=(const QQmlTypeModuleVersion &o) +{ + m_module = o.m_module; + m_minor = o.m_minor; + return *this; +} + +QQmlTypeModule *QQmlTypeModuleVersion::module() const +{ + return m_module; +} + +int QQmlTypeModuleVersion::minorVersion() const +{ + return m_minor; +} + +QQmlType QQmlTypeModuleVersion::type(const QHashedStringRef &name) const +{ + if (!m_module) + return QQmlType(); + return m_module->type(name, m_minor); +} + +QQmlType QQmlTypeModuleVersion::type(const QV4::String *name) const +{ + if (!m_module) + return QQmlType(); + return m_module->type(name, m_minor); +} + +QT_END_NAMESPACE diff --git a/src/qml/qml/qqmltypemoduleversion_p.h b/src/qml/qml/qqmltypemoduleversion_p.h new file mode 100644 index 0000000000..20f4709ecb --- /dev/null +++ b/src/qml/qml/qqmltypemoduleversion_p.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** 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 QQMLTYPEMODULEVERSION_P_H +#define QQMLTYPEMODULEVERSION_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 <QtQml/qtqmlglobal.h> + +QT_BEGIN_NAMESPACE + +class QQmlTypeModule; +class QQmlType; +class QHashedStringRef; + +namespace QV4 { +struct String; +} + +class QQmlTypeModuleVersion +{ +public: + QQmlTypeModuleVersion(); + QQmlTypeModuleVersion(QQmlTypeModule *, int); + QQmlTypeModuleVersion(const QQmlTypeModuleVersion &); + QQmlTypeModuleVersion &operator=(const QQmlTypeModuleVersion &); + + QQmlTypeModule *module() const; + int minorVersion() const; + + QQmlType type(const QHashedStringRef &) const; + QQmlType type(const QV4::String *) const; + +private: + QQmlTypeModule *m_module; + int m_minor; +}; + +QT_END_NAMESPACE + +#endif // QQMLTYPEMODULEVERSION_P_H diff --git a/src/qml/qml/qqmltypenamecache_p.h b/src/qml/qml/qqmltypenamecache_p.h index 28b5e7f0ad..b98fe77ed5 100644 --- a/src/qml/qml/qqmltypenamecache_p.h +++ b/src/qml/qml/qqmltypenamecache_p.h @@ -55,8 +55,9 @@ #include "qqmlcleanup_p.h" #include "qqmlmetatype_p.h" -#include <private/qhashedstring_p.h> +#include <private/qstringhash_p.h> #include <private/qqmlimport_p.h> +#include <private/qqmltypemoduleversion_p.h> #include <QtCore/qvector.h> diff --git a/src/qml/qml/qqmltypewrapper.cpp b/src/qml/qml/qqmltypewrapper.cpp index d30c225741..86513b5ff8 100644 --- a/src/qml/qml/qqmltypewrapper.cpp +++ b/src/qml/qml/qqmltypewrapper.cpp @@ -38,10 +38,10 @@ ****************************************************************************/ #include "qqmltypewrapper_p.h" -#include <private/qv8engine_p.h> #include <private/qqmlengine_p.h> #include <private/qqmlcontext_p.h> +#include <private/qqmlmetaobject_p.h> #include <private/qjsvalue_p.h> #include <private/qv4functionobject_p.h> diff --git a/src/qml/qml/qqmltypewrapper_p.h b/src/qml/qml/qqmltypewrapper_p.h index 44e82dec2b..c797a4ac10 100644 --- a/src/qml/qml/qqmltypewrapper_p.h +++ b/src/qml/qml/qqmltypewrapper_p.h @@ -81,7 +81,7 @@ struct QQmlTypeWrapper : Object { QQmlType type() const; - QQmlTypePrivate *typePrivate; + const QQmlTypePrivate *typePrivate; QQmlTypeNameCache *typeNamespace; const QQmlImportRef *importNamespace; }; @@ -90,7 +90,7 @@ struct QQmlScopedEnumWrapper : Object { void init() { Object::init(); } void destroy(); int scopeEnumIndex; - QQmlTypePrivate *typePrivate; + const QQmlTypePrivate *typePrivate; QQmlType type() const; }; diff --git a/src/qml/qml/qqmlvaluetype.cpp b/src/qml/qml/qqmlvaluetype.cpp index e92488f9f6..21505754bb 100644 --- a/src/qml/qml/qqmlvaluetype.cpp +++ b/src/qml/qml/qqmlvaluetype.cpp @@ -38,7 +38,6 @@ ****************************************************************************/ #include "qqmlvaluetype_p.h" -#include "qqmlmetatype_p.h" #include <private/qqmlglobal_p.h> #include <QtCore/qdebug.h> 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 <private/qv8engine_p.h> + #include <private/qqmlvaluetype_p.h> #include <private/qqmlbinding_p.h> #include <private/qqmlglobal_p.h> 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<QObject *> *QQmlVMEMetaObject::readPropertyAsList(int id) const QV4::Scope scope(engine); QV4::Scoped<QV4::VariantObject> v(scope, *(md->data() + id)); if (!v || (int)v->d()->data().userType() != qMetaTypeId<QList<QObject *> >()) { - QVariant variant(qVariantFromValue(QList<QObject*>())); + QVariant variant(QVariant::fromValue(QList<QObject*>())); v = engine->newVariantObject(variant); md->set(engine, id, v); } diff --git a/src/qml/qml/qqmlvmemetaobject_p.h b/src/qml/qml/qqmlvmemetaobject_p.h index dbcc9d2884..2371d70f10 100644 --- a/src/qml/qml/qqmlvmemetaobject_p.h +++ b/src/qml/qml/qqmlvmemetaobject_p.h @@ -65,9 +65,7 @@ #include "qqmlguard_p.h" #include "qqmlcontext_p.h" -#include "qqmlpropertycache_p.h" -#include <private/qv8engine_p.h> #include <private/qflagpointer_p.h> #include <private/qv4object_p.h> 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<QQmlAdaptorModel>()) { @@ -1139,20 +1139,20 @@ QString QQmlDelegateModelPrivate::stringValue(Compositor::Group group, int index while (dot > 0) { QObject *obj = qvariant_cast<QObject*>(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<QByteArray> &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<QByteArray> &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<QByteArray> watchedRoles() const { return m_watchedRoles; } void setWatchedRoles(const QList<QByteArray> &roles) override; QQmlIncubator::Status incubationStatus(int index) override; diff --git a/src/qml/types/qqmlmodelsmodule.cpp b/src/qml/types/qqmlmodelsmodule.cpp index bb72771cd9..5423054934 100644 --- a/src/qml/types/qqmlmodelsmodule.cpp +++ b/src/qml/types/qqmlmodelsmodule.cpp @@ -47,6 +47,8 @@ #include <private/qqmldelegatecomponent_p.h> #endif #include <private/qqmlobjectmodel_p.h> +#include <private/qqmltablemodel_p.h> +#include <private/qqmltablemodelcolumn_p.h> QT_BEGIN_NAMESPACE @@ -77,6 +79,8 @@ void QQmlModelsModule::defineLabsModule() qmlRegisterType<QQmlDelegateChooser>(uri, 1, 0, "DelegateChooser"); qmlRegisterType<QQmlDelegateChoice>(uri, 1, 0, "DelegateChoice"); #endif + qmlRegisterType<QQmlTableModel>(uri, 1, 0, "TableModel"); + qmlRegisterType<QQmlTableModelColumn>(uri, 1, 0, "TableModelColumn"); } QT_END_NAMESPACE 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<QByteArray> &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<QByteArray> &) 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<QByteArray> &) override { Q_UNREACHABLE(); } int indexOf(QObject *, QObject *) const override { Q_UNREACHABLE(); return 0; } diff --git a/src/qml/types/qqmltablemodel.cpp b/src/qml/types/qqmltablemodel.cpp new file mode 100644 index 0000000000..4a96e7a46b --- /dev/null +++ b/src/qml/types/qqmltablemodel.cpp @@ -0,0 +1,1059 @@ +/**************************************************************************** +** +** 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 "qqmltablemodel_p.h" + +#include <QtCore/qloggingcategory.h> +#include <QtQml/qqmlinfo.h> +#include <QtQml/qqmlengine.h> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcTableModel, "qt.qml.tablemodel") + +/*! + \qmltype TableModel + \instantiates QQmlTableModel + \inqmlmodule Qt.labs.qmlmodels + \brief Encapsulates a simple table model. + \since 5.14 + + 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 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 + directly via the \l rows property, but it is not possible to + modify the model data this way. + + To add new rows, use \l appendRow() and \l insertRow(). To modify + existing rows, use \l setRow(), \l moveRow(), \l removeRow(), and + \l clear(). + + It is also possible to modify the model's data via the delegate, + as shown in the example above: + + \snippet qml/tablemodel/fruit-example-simpledelegate.qml delegate + + If the type of the data at the modified role does not match the type of the + data that is set, it will be automatically converted via + \l {QVariant::canConvert()}{QVariant}. + + \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 + + For most real world use cases, it is recommended to use DelegateChooser + as the delegate of a TableView that uses TableModel. This allows you to + use specific roles in the relevant delegates. For example, the snippet + above can be rewritten to use DelegateChooser like so: + + \snippet qml/tablemodel/fruit-example-delegatechooser.qml file + + The most specific delegates are declared first: the columns at index \c 0 + and \c 1 have \c bool and \c integer data types, so they use a + \l [QtQuickControls2]{CheckBox} and \l [QtQuickControls2]{SpinBox}, + respectively. The remaining columns can simply use a + \l [QtQuickControls2]{TextField}, and so that delegate is declared + last as a fallback. + + \sa TableModelColumn, TableView, QAbstractTableModel +*/ + +QQmlTableModel::QQmlTableModel(QObject *parent) + : QAbstractTableModel(parent) +{ +} + +QQmlTableModel::~QQmlTableModel() +{ +} + +/*! + \qmlproperty object TableModel::rows + + This property holds the model data in the form of an array of rows: + + \snippet qml/tablemodel/fruit-example-simpledelegate.qml rows + + \sa getRow(), setRow(), moveRow(), appendRow(), insertRow(), clear(), rowCount, columnCount +*/ +QVariant QQmlTableModel::rows() const +{ + return mRows; +} + +void QQmlTableModel::setRows(const QVariant &rows) +{ + if (rows.userType() != qMetaTypeId<QJSValue>()) { + qmlWarning(this) << "setRows(): \"rows\" must be an array; actual type is " << rows.typeName(); + return; + } + + const QJSValue rowsAsJSValue = rows.value<QJSValue>(); + const QVariantList rowsAsVariantList = rowsAsJSValue.toVariant().toList(); + if (rowsAsVariantList == mRows) { + // No change. + return; + } + + if (!componentCompleted) { + // Store the rows until we can call doSetRows() after component completion. + mRows = rowsAsVariantList; + return; + } + + doSetRows(rowsAsVariantList); +} + +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; + } + } + + const int oldRowCount = mRowCount; + const int oldColumnCount = mColumnCount; + + beginResetModel(); + + // We don't clear the column or role data, because a TableModel should not be reused in that way. + // Once it has valid data, its columns and roles are fixed. + mRows = rowsAsVariantList; + mRowCount = mRows.size(); + + // Gather metadata the first time rows is set. + if (firstTimeValidRowsHaveBeenSet && !mRows.isEmpty()) + fetchColumnMetadata(); + + endResetModel(); + + emit rowsChanged(); + + if (mRowCount != oldRowCount) + emit rowCountChanged(); + if (mColumnCount != oldColumnCount) + 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) + + Adds a new row to the end of the model, with the + values (cells) in \a row. + + \code + model.appendRow({ + checkable: true, + amount: 1, + fruitType: "Pear", + fruitName: "Williams", + fruitPrice: 1.50, + }) + \endcode + + \sa insertRow(), setRow(), removeRow() +*/ +void QQmlTableModel::appendRow(const QVariant &row) +{ + if (!validateNewRow("appendRow()", row, -1, AppendOperation)) + return; + + doInsert(mRowCount, row); +} + +/*! + \qmlmethod TableModel::clear() + + Removes all rows from the model. + + \sa removeRow() +*/ +void QQmlTableModel::clear() +{ + QQmlEngine *engine = qmlEngine(this); + Q_ASSERT(engine); + setRows(QVariant::fromValue(engine->newArray())); +} + +/*! + \qmlmethod object TableModel::getRow(int rowIndex) + + Returns the row at \a rowIndex in the model. + + Note that this equivalent to accessing the row directly + through the \l rows property: + + \code + Component.onCompleted: { + // These two lines are equivalent. + console.log(model.getRow(0).display); + console.log(model.rows[0].fruitName); + } + \endcode + + \note the returned object cannot be used to modify the contents of the + model; use setRow() instead. + + \sa setRow(), appendRow(), insertRow(), removeRow(), moveRow() +*/ +QVariant QQmlTableModel::getRow(int rowIndex) +{ + if (!validateRowIndex("getRow()", "rowIndex", rowIndex)) + return QVariant(); + + return mRows.at(rowIndex); +} + +/*! + \qmlmethod TableModel::insertRow(int rowIndex, object row) + + Adds a new row to the list model at position \a rowIndex, with the + values (cells) in \a row. + + \code + 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 + the end of the list (equivalent to \l appendRow()). + + \sa appendRow(), setRow(), removeRow(), rowCount +*/ +void QQmlTableModel::insertRow(int rowIndex, const QVariant &row) +{ + if (!validateNewRow("insertRow()", row, rowIndex)) + return; + + doInsert(rowIndex, row); +} + +void QQmlTableModel::doInsert(int rowIndex, const QVariant &row) +{ + beginInsertRows(QModelIndex(), rowIndex, rowIndex); + + // Adding rowAsVariant.toList() will add each invidual variant in the list, + // which is definitely not what we want. + const QVariant rowAsVariant = row.value<QJSValue>().toVariant(); + mRows.insert(rowIndex, rowAsVariant); + ++mRowCount; + + 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) + + Moves \a rows from the index at \a fromRowIndex to the index at + \a toRowIndex. + + The from and to ranges must exist; for example, to move the first 3 items + to the end of the list: + + \code + model.moveRow(0, model.rowCount - 3, 3) + \endcode + + \sa appendRow(), insertRow(), removeRow(), rowCount +*/ +void QQmlTableModel::moveRow(int fromRowIndex, int toRowIndex, int rows) +{ + if (fromRowIndex == toRowIndex) { + qmlWarning(this) << "moveRow(): \"fromRowIndex\" cannot be equal to \"toRowIndex\""; + return; + } + + if (rows <= 0) { + qmlWarning(this) << "moveRow(): \"rows\" is less than or equal to 0"; + return; + } + + if (!validateRowIndex("moveRow()", "fromRowIndex", fromRowIndex)) + return; + + if (!validateRowIndex("moveRow()", "toRowIndex", toRowIndex)) + return; + + if (fromRowIndex + rows > mRowCount) { + qmlWarning(this) << "moveRow(): \"fromRowIndex\" (" << fromRowIndex + << ") + \"rows\" (" << rows << ") = " << (fromRowIndex + rows) + << ", which is greater than rowCount() of " << mRowCount; + return; + } + + if (toRowIndex + rows > mRowCount) { + qmlWarning(this) << "moveRow(): \"toRowIndex\" (" << toRowIndex + << ") + \"rows\" (" << rows << ") = " << (toRowIndex + rows) + << ", which is greater than rowCount() of " << mRowCount; + return; + } + + qCDebug(lcTableModel).nospace() << "moving " << rows + << " row(s) from index " << fromRowIndex + << " to index " << toRowIndex; + + // Based on the same call in QQmlListModel::moveRow(). + beginMoveRows(QModelIndex(), fromRowIndex, fromRowIndex + rows - 1, QModelIndex(), + toRowIndex > fromRowIndex ? toRowIndex + rows : toRowIndex); + + // Based on ListModel::moveRow(). + if (fromRowIndex > toRowIndex) { + // Only move forwards - flip if moving backwards. + const int from = fromRowIndex; + const int to = toRowIndex; + fromRowIndex = to; + toRowIndex = to + rows; + rows = from - to; + } + + QVector<QVariant> store; + store.reserve(rows); + for (int i = 0; i < (toRowIndex - fromRowIndex); ++i) + store.append(mRows.at(fromRowIndex + rows + i)); + for (int i = 0; i < rows; ++i) + store.append(mRows.at(fromRowIndex + i)); + for (int i = 0; i < store.size(); ++i) + mRows[fromRowIndex + i] = store[i]; + + qCDebug(lcTableModel).nospace() << "after moving, rows are:\n" << mRows; + + endMoveRows(); +} + +/*! + \qmlmethod TableModel::removeRow(int rowIndex, int rows = 1) + + Removes the row at \a rowIndex from the model. + + \sa clear(), rowCount +*/ +void QQmlTableModel::removeRow(int rowIndex, int rows) +{ + if (!validateRowIndex("removeRow()", "rowIndex", rowIndex)) + return; + + if (rows <= 0) { + qmlWarning(this) << "removeRow(): \"rows\" is less than or equal to zero"; + return; + } + + if (rowIndex + rows - 1 >= mRowCount) { + qmlWarning(this) << "removeRow(): \"rows\" " << rows + << " exceeds available rowCount() of " << mRowCount + << " when removing from \"rowIndex\" " << rowIndex; + return; + } + + beginRemoveRows(QModelIndex(), rowIndex, rowIndex + rows - 1); + + auto firstIterator = mRows.begin() + rowIndex; + // The "last" argument to erase() is exclusive, so we go one past the last item. + auto lastIterator = firstIterator + rows; + mRows.erase(firstIterator, lastIterator); + mRowCount -= rows; + + endRemoveRows(); + emit rowCountChanged(); + + qCDebug(lcTableModel).nospace() << "removed " << rows + << " items from the model, starting at index " << rowIndex; +} + +/*! + \qmlmethod TableModel::setRow(int rowIndex, object row) + + Changes the row at \a rowIndex in the model with \a row. + + All columns/cells must be present in \c row, and in the correct order. + + \code + 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 + model. Otherwise, \a rowIndex must point to an existing row in the model. + + \sa appendRow(), insertRow(), rowCount +*/ +void QQmlTableModel::setRow(int rowIndex, const QVariant &row) +{ + if (!validateNewRow("setRow()", row, rowIndex)) + return; + + if (rowIndex != mRowCount) { + // Setting an existing row. + mRows[rowIndex] = row; + + // For now we just assume the whole row changed, as it's simpler. + const QModelIndex topLeftModelIndex(createIndex(rowIndex, 0)); + const QModelIndex bottomRightModelIndex(createIndex(rowIndex, mColumnCount - 1)); + emit dataChanged(topLeftModelIndex, bottomRightModelIndex); + } else { + // Appending a row. + doInsert(rowIndex, row); + } +} + +QQmlListProperty<QQmlTableModelColumn> QQmlTableModel::columns() +{ + return QQmlListProperty<QQmlTableModelColumn>(this, nullptr, + &QQmlTableModel::columns_append, + &QQmlTableModel::columns_count, + &QQmlTableModel::columns_at, + &QQmlTableModel::columns_clear); +} + +void QQmlTableModel::columns_append(QQmlListProperty<QQmlTableModelColumn> *property, + QQmlTableModelColumn *value) +{ + QQmlTableModel *model = static_cast<QQmlTableModel*>(property->object); + QQmlTableModelColumn *column = qobject_cast<QQmlTableModelColumn*>(value); + if (column) + model->mColumns.append(column); +} + +int QQmlTableModel::columns_count(QQmlListProperty<QQmlTableModelColumn> *property) +{ + const QQmlTableModel *model = static_cast<QQmlTableModel*>(property->object); + return model->mColumns.count(); +} + +QQmlTableModelColumn *QQmlTableModel::columns_at(QQmlListProperty<QQmlTableModelColumn> *property, int index) +{ + const QQmlTableModel *model = static_cast<QQmlTableModel*>(property->object); + return model->mColumns.at(index); +} + +void QQmlTableModel::columns_clear(QQmlListProperty<QQmlTableModelColumn> *property) +{ + QQmlTableModel *model = static_cast<QQmlTableModel*>(property->object); + return model->mColumns.clear(); +} + +/*! + \qmlmethod QModelIndex TableModel::index(int row, int column) + + Returns a \l QModelIndex object referencing the given \a row and \a column, + which can be passed to the data() function to get the data from that cell, + or to setData() to edit the contents of that cell. + + \code + import QtQml 2.14 + import Qt.labs.qmlmodels 1.0 + + TableModel { + id: model + + TableModelColumn { display: "fruitType" } + TableModelColumn { display: "fruitPrice" } + + rows: [ + { 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)).display + + " costs " + model.data(model.index(r, 1)).display.toFixed(2)) + } + } + } + \endcode + + \sa {QModelIndex and related Classes in QML}, data() +*/ +// Note: we don't document the parent argument, because you never need it, because +// cells in a TableModel don't have parents. But it is there because this function is an override. +QModelIndex QQmlTableModel::index(int row, int column, const QModelIndex &parent) const +{ + return row >= 0 && row < rowCount() && column >= 0 && column < columnCount() && !parent.isValid() + ? createIndex(row, column) + : QModelIndex(); +} + +/*! + \qmlproperty int TableModel::rowCount + \readonly + + This read-only property holds the number of rows in the model. + + This value changes whenever rows are added or removed from the model. +*/ +int QQmlTableModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + + return mRowCount; +} + +/*! + \qmlproperty int TableModel::columnCount + \readonly + + This read-only property holds the number of columns in the model. + + The number of columns is fixed for the lifetime of the model + after the \l rows property is set or \l appendRow() is called for the first + time. +*/ +int QQmlTableModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + + return mColumnCount; +} + +/*! + \qmlmethod variant TableModel::data(QModelIndex index, string role) + + Returns the data from the table cell at the given \a index belonging to the + given \a role. + + \sa index() +*/ +QVariant QQmlTableModel::data(const QModelIndex &index, const QString &role) const +{ + const int iRole = mRoleNames.key(role.toUtf8(), -1); + if (iRole >= 0) + return data(index, iRole); + return QVariant(); +} + +QVariant QQmlTableModel::data(const QModelIndex &index, int role) const +{ + const int row = index.row(); + if (row < 0 || row >= rowCount()) + return QVariant(); + + const int column = index.column(); + if (column < 0 || column >= columnCount()) + return QVariant(); + + 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 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; + } + + // 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(); +} + +/*! + \qmlmethod bool TableModel::setData(QModelIndex index, string role, variant value) + + Inserts or updates the data field named by \a role in the table cell at the + given \a index with \a value. Returns true if sucessful, false if not. + + \sa index() +*/ +bool QQmlTableModel::setData(const QModelIndex &index, const QString &role, const QVariant &value) +{ + const int intRole = mRoleNames.key(role.toUtf8(), -1); + if (intRole >= 0) + return setData(index, value, intRole); + return false; +} + +bool QQmlTableModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + const int row = index.row(); + if (row < 0 || row >= rowCount()) + return false; + + const int column = index.column(); + if (column < 0 || column >= columnCount()) + return false; + + const QString roleName = QString::fromUtf8(mRoleNames.value(role)); + + qCDebug(lcTableModel).nospace() << "setData() called with index " + << index << ", value " << value << " and role " << roleName; + + // Verify that the role exists for this column. + 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() != roleData.type) { + if (!value.canConvert(int(roleData.type))) { + qmlWarning(this).nospace() << "setData(): the value " << value + << " set at row " << row << " column " << column << " with role " << roleName + << " cannot be converted to " << roleData.typeName; + return false; + } + + if (!effectiveValue.convert(int(roleData.type))) { + qmlWarning(this).nospace() << "setData(): failed converting value " << value + << " set at row " << row << " column " << column << " with role " << roleName + << " to " << roleData.typeName; + return false; + } + } + + 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: + + - 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<int> rolesChanged; + rolesChanged.append(role); + emit dataChanged(index, index, rolesChanged); + + return true; +} + +QHash<int, QByteArray> QQmlTableModel::roleNames() const +{ + return mRoleNames; +} + +QQmlTableModel::ColumnRoleMetadata::ColumnRoleMetadata() +{ +} + +QQmlTableModel::ColumnRoleMetadata::ColumnRoleMetadata( + bool isStringRole, const QString &name, QVariant::Type type, const QString &typeName) : + isStringRole(isStringRole), + name(name), + type(type), + typeName(typeName) +{ +} + +bool QQmlTableModel::ColumnRoleMetadata::isValid() const +{ + return !name.isEmpty(); +} + +bool QQmlTableModel::validateRowType(const char *functionName, const QVariant &row) const +{ + if (!row.canConvert<QJSValue>()) { + qmlWarning(this) << functionName << ": expected \"row\" argument to be a QJSValue," + << " but got " << row.typeName() << " instead:\n" << row; + return false; + } + + const QJSValue rowAsJSValue = row.value<QJSValue>(); + if (!rowAsJSValue.isObject() && !rowAsJSValue.isArray()) { + qmlWarning(this) << functionName << ": expected \"row\" argument " + << "to be an object or array, but got:\n" << rowAsJSValue.toString(); + return false; + } + + return true; +} + +bool QQmlTableModel::validateNewRow(const char *functionName, const QVariant &row, + int rowIndex, NewRowOperationFlag operation) const +{ + 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 (operation == OtherOperation) { + // Inserting/setting. + if (rowIndex < 0) { + qmlWarning(this) << functionName << ": \"rowIndex\" cannot be negative"; + return false; + } + + if (rowIndex > mRowCount) { + qmlWarning(this) << functionName << ": \"rowIndex\" " << rowIndex + << " is greater than rowCount() of " << mRowCount; + return false; + } + } + + const QVariant rowAsVariant = operation == SetRowsOperation + ? row : row.value<QJSValue>().toVariant(); + if (rowAsVariant.type() != QVariant::Map) { + qmlWarning(this) << functionName << ": row manipulation functions " + << "do not support complex rows (row index: " << rowIndex << ")"; + return false; + } + + const QVariantMap rowAsMap = rowAsVariant.toMap(); + const int columnCount = rowAsMap.size(); + if (columnCount < mColumnCount) { + qmlWarning(this) << functionName << ": expected " << mColumnCount + << " columns, but only got " << columnCount; + 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<QString, QJSValue> 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; +} + +bool QQmlTableModel::validateRowIndex(const char *functionName, const char *argumentName, int rowIndex) const +{ + if (rowIndex < 0) { + qmlWarning(this) << functionName << ": \"" << argumentName << "\" cannot be negative"; + return false; + } + + if (rowIndex >= mRowCount) { + qmlWarning(this) << functionName << ": \"" << argumentName + << "\" " << rowIndex << " is greater than or equal to rowCount() of " << mRowCount; + return false; + } + + return true; +} + +QT_END_NAMESPACE diff --git a/src/qml/types/qqmltablemodel_p.h b/src/qml/types/qqmltablemodel_p.h new file mode 100644 index 0000000000..a1bb97e7d4 --- /dev/null +++ b/src/qml/types/qqmltablemodel_p.h @@ -0,0 +1,170 @@ +/**************************************************************************** +** +** 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 QQMLTABLEMODEL_P_H +#define QQMLTABLEMODEL_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 <QtCore/QObject> +#include <QtCore/QAbstractTableModel> +#include <QtQml/qqml.h> +#include <QtQml/private/qtqmlglobal_p.h> +#include <QtQml/private/qqmltablemodelcolumn_p.h> +#include <QtQml/QJSValue> +#include <QtQml/QQmlListProperty> + +QT_BEGIN_NAMESPACE + +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(QQmlListProperty<QQmlTableModelColumn> columns READ columns CONSTANT FINAL) + Q_INTERFACES(QQmlParserStatus) + Q_CLASSINFO("DefaultProperty", "columns") + +public: + QQmlTableModel(QObject *parent = nullptr); + ~QQmlTableModel() override; + + QVariant rows() const; + void setRows(const QVariant &rows); + + Q_INVOKABLE void appendRow(const QVariant &row); + Q_INVOKABLE void clear(); + Q_INVOKABLE QVariant getRow(int rowIndex); + Q_INVOKABLE void insertRow(int rowIndex, const QVariant &row); + Q_INVOKABLE void moveRow(int fromRowIndex, int toRowIndex, int rows = 1); + Q_INVOKABLE void removeRow(int rowIndex, int rows = 1); + Q_INVOKABLE void setRow(int rowIndex, const QVariant &row); + + QQmlListProperty<QQmlTableModelColumn> columns(); + + static void columns_append(QQmlListProperty<QQmlTableModelColumn> *property, QQmlTableModelColumn *value); + static int columns_count(QQmlListProperty<QQmlTableModelColumn> *property); + static QQmlTableModelColumn *columns_at(QQmlListProperty<QQmlTableModelColumn> *property, int index); + static void columns_clear(QQmlListProperty<QQmlTableModelColumn> *property); + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + Q_INVOKABLE QVariant data(const QModelIndex &index, const QString &role) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + Q_INVOKABLE bool setData(const QModelIndex &index, const QString &role, const QVariant &value); + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::DisplayRole) override; + QHash<int, QByteArray> roleNames() const override; + +Q_SIGNALS: + void columnCountChanged(); + void rowCountChanged(); + void rowsChanged(); + +private: + class ColumnRoleMetadata + { + public: + 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 ColumnMetadata + { + // 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<QString, ColumnRoleMetadata> 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 operation = OtherOperation) const; + bool validateRowIndex(const char *functionName, const char *argumentName, int rowIndex) const; + + void doInsert(int rowIndex, const QVariant &row); + + void classBegin() override; + void componentComplete() override; + + bool componentCompleted = false; + QVariantList mRows; + QList<QQmlTableModelColumn *> mColumns; + int mRowCount = 0; + int mColumnCount = 0; + // Each entry contains information about the properties of the column at that index. + QVector<ColumnMetadata> mColumnMetadata; + // key = property index (0 to number of properties across all columns) + // value = role name + QHash<int, QByteArray> mRoleNames; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQmlTableModel) + +#endif // QQMLTABLEMODEL_P_H 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 <QtQml/qqmlinfo.h> + +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<QString, QJSValue> QQmlTableModelColumn::getters() const +{ + return mGetters; +} + +const QHash<int, QString> QQmlTableModelColumn::supportedRoleNames() +{ + QHash<int, QString> 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 <QtCore/QObject> +#include <QtQml/qqml.h> +#include <QtQml/private/qtqmlglobal_p.h> +#include <QtQml/qjsvalue.h> + +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<QString, QJSValue> getters() const; + + static const QHash<int, QString> 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<QString, QJSValue> mGetters; + QHash<QString, QJSValue> mSetters; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQmlTableModelColumn) + +#endif // QQMLTABLEMODELCOLUMN_P_H 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 <private/qqmlengine_p.h> #include <private/qqmlexpression_p.h> diff --git a/src/qml/types/types.pri b/src/qml/types/types.pri index e74c89b1f1..5a56208dc4 100644 --- a/src/qml/types/types.pri +++ b/src/qml/types/types.pri @@ -6,7 +6,9 @@ SOURCES += \ $$PWD/qqmlobjectmodel.cpp \ $$PWD/qquickpackage.cpp \ $$PWD/qqmlinstantiator.cpp \ - $$PWD/qqmltableinstancemodel.cpp + $$PWD/qqmltableinstancemodel.cpp \ + $$PWD/qqmltablemodel.cpp \ + $$PWD/qqmltablemodelcolumn.cpp HEADERS += \ $$PWD/qqmlbind_p.h \ @@ -17,7 +19,9 @@ HEADERS += \ $$PWD/qquickpackage_p.h \ $$PWD/qqmlinstantiator_p.h \ $$PWD/qqmlinstantiator_p_p.h \ - $$PWD/qqmltableinstancemodel_p.h + $$PWD/qqmltableinstancemodel_p.h \ + $$PWD/qqmltablemodel_p.h \ + $$PWD/qqmltablemodelcolumn_p.h qtConfig(qml-worker-script) { SOURCES += \ |