From 8e466a30af34c5bb83e7fc591ddf569e2ec79d9f Mon Sep 17 00:00:00 2001 From: Aaron Kennedy Date: Mon, 14 Dec 2009 13:42:55 +1000 Subject: Introduce experimental binding optimizer Enable with QML_EXPERIMENTAL=1 --- src/declarative/qml/qml.pri | 2 + src/declarative/qml/qmlbasicscript.cpp | 10 + src/declarative/qml/qmlbindingoptimizations.cpp | 87 ++ src/declarative/qml/qmlbindingoptimizations_p.h | 34 + src/declarative/qml/qmlbindingvme.cpp | 1519 +++++++++++++++++++++++ src/declarative/qml/qmlbindingvme_p.h | 95 ++ src/declarative/qml/qmlcompiler.cpp | 41 +- src/declarative/qml/qmlcompiler_p.h | 5 +- src/declarative/qml/qmlcontext_p.h | 4 +- src/declarative/qml/qmlinstruction.cpp | 3 + src/declarative/qml/qmlinstruction_p.h | 3 +- src/declarative/qml/qmlvme.cpp | 21 + src/script/bridge/qscriptdeclarativeclass.cpp | 1 + 13 files changed, 1818 insertions(+), 7 deletions(-) create mode 100644 src/declarative/qml/qmlbindingvme.cpp create mode 100644 src/declarative/qml/qmlbindingvme_p.h diff --git a/src/declarative/qml/qml.pri b/src/declarative/qml/qml.pri index 1155a36..172146c 100644 --- a/src/declarative/qml/qml.pri +++ b/src/declarative/qml/qml.pri @@ -32,6 +32,7 @@ SOURCES += \ $$PWD/qmlenginedebug.cpp \ $$PWD/qmlrewrite.cpp \ $$PWD/qmlbasicscript.cpp \ + $$PWD/qmlbindingvme.cpp \ $$PWD/qmlvaluetype.cpp \ $$PWD/qmlbindingoptimizations.cpp \ $$PWD/qmlxmlhttprequest.cpp \ @@ -94,6 +95,7 @@ HEADERS += \ $$PWD/qmlerror.h \ $$PWD/qmlscriptparser_p.h \ $$PWD/qmlbasicscript_p.h \ + $$PWD/qmlbindingvme_p.h \ $$PWD/qmlenginedebug_p.h \ $$PWD/qmlrewrite_p.h \ $$PWD/qpodvector_p.h \ diff --git a/src/declarative/qml/qmlbasicscript.cpp b/src/declarative/qml/qmlbasicscript.cpp index 6a32df7..b596437 100644 --- a/src/declarative/qml/qmlbasicscript.cpp +++ b/src/declarative/qml/qmlbasicscript.cpp @@ -121,6 +121,16 @@ public: } }; +// anchors.left: (nop convert)(qobject)parent.(anchorline)right + +/* + Property chains: a.b.c.d + if else value selection statements: if(a) b else if(c) d else e + trinary selection: a?b:c + addition: a + b + negation: a - a + equality: == +*/ static QVariant fetch_value(QObject *o, int idx, int type) { if (!o) diff --git a/src/declarative/qml/qmlbindingoptimizations.cpp b/src/declarative/qml/qmlbindingoptimizations.cpp index 0f2e199..120e07b 100644 --- a/src/declarative/qml/qmlbindingoptimizations.cpp +++ b/src/declarative/qml/qmlbindingoptimizations.cpp @@ -42,9 +42,96 @@ #include "qmlbindingoptimizations_p.h" #include "qmlcontext_p.h" +#include +#include "qmlbindingvme_p.h" QT_BEGIN_NAMESPACE +int QmlBinding_Basic::reevalIndex = -1; + +QmlBinding_Basic::QmlBinding_Basic(QObject *target, int property, + const char *data, QmlRefCount *ref, + QObject *scope, QmlContext *context) +: m_enabled(false), m_updating(false), m_scope(scope), m_target(target), + m_property(property), m_data(data) +{ + if (reevalIndex == -1) + reevalIndex = QmlBinding_Basic::staticMetaObject.indexOfSlot("reeval()"); + + m_config.target = this; + m_config.targetSlot = reevalIndex; + m_scope2 = m_scope; + + QmlAbstractExpression::setContext(context); +} + +QmlBinding_Basic::~QmlBinding_Basic() +{ +} + +void QmlBinding_Basic::setEnabled(bool e, QmlMetaProperty::WriteFlags flags) +{ + if (e) { + addToObject(m_target); + update(flags); + } else { + removeFromObject(); + } + + QmlAbstractBinding::setEnabled(e, flags); + + if (m_enabled != e) { + m_enabled = e; + + if (e) update(flags); + } +} + +int QmlBinding_Basic::propertyIndex() +{ + return m_property & 0xFFFF; +} + +void QmlBinding_Basic::update(QmlMetaProperty::WriteFlags flags) +{ + if (!m_enabled) + return; + + if (m_updating) { + qmlInfo(m_target) << tr("Binding loop detected"); + return; + } + + QmlContext *context = QmlAbstractExpression::context(); + if (!context) + return; + QmlContextPrivate *cp = QmlContextPrivate::get(context); + + m_updating = true; + + if (m_property & 0xFFFF0000) { + QmlEnginePrivate *ep = QmlEnginePrivate::get(cp->engine); + + QmlValueType *vt = ep->valueTypes[(m_property >> 16) & 0xFF]; + Q_ASSERT(vt); + vt->read(m_target, m_property & 0xFFFF); + + QObject *target = vt; + QmlBindingVME::run(m_data, &m_config, cp, &m_scope, &target); + + vt->write(m_target, m_property & 0xFFFF, flags); + } else { + QmlBindingVME::run(m_data, &m_config, cp, &m_scope, &m_target); + } + + m_updating = false; +} + +void QmlBinding_Basic::reeval() +{ + update(QmlMetaProperty::DontRemoveBinding); +} + /* The QmlBinding_Id optimization handles expressions of the type: diff --git a/src/declarative/qml/qmlbindingoptimizations_p.h b/src/declarative/qml/qmlbindingoptimizations_p.h index 4ce145d..ee2eb83 100644 --- a/src/declarative/qml/qmlbindingoptimizations_p.h +++ b/src/declarative/qml/qmlbindingoptimizations_p.h @@ -55,11 +55,45 @@ #include "qmlexpression_p.h" #include "qmlbinding.h" +#include "qmlbasicscript_p.h" +#include "qmlbindingvme_p.h" QT_BEGIN_HEADER QT_BEGIN_NAMESPACE +class QmlBinding_Basic : public QObject, + public QmlAbstractExpression, + public QmlAbstractBinding +{ + Q_OBJECT +public: + QmlBinding_Basic(QObject *target, int property, + const char *data, QmlRefCount *ref, + QObject *scope, QmlContext *context); + virtual ~QmlBinding_Basic(); + + // Inherited from QmlAbstractBinding + virtual void setEnabled(bool, QmlMetaProperty::WriteFlags flags); + virtual int propertyIndex(); + virtual void update(QmlMetaProperty::WriteFlags flags); + +private slots: + void reeval(); + +private: + bool m_enabled:1; + bool m_updating:1; + QObject *m_scope; + QObject *m_target; + int m_property; + const char *m_data; + QmlBindingVME::Config m_config; + QGuard m_scope2; + + static int reevalIndex; +}; + class QmlBinding_Id : public QmlAbstractExpression, public QmlAbstractBinding { diff --git a/src/declarative/qml/qmlbindingvme.cpp b/src/declarative/qml/qmlbindingvme.cpp new file mode 100644 index 0000000..26dd303 --- /dev/null +++ b/src/declarative/qml/qmlbindingvme.cpp @@ -0,0 +1,1519 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmlbindingvme_p.h" +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace QmlJS; + +namespace { +// Supported types: int, qreal, QString (needs constr/destr), QObject*, bool +struct Register { + void setQObject(QObject *o) { *((QObject **)data) = o; } + QObject *getQObject() { return *((QObject **)data); } + + void setqreal(qreal v) { *((qreal *)data) = v; } + qreal getqreal() { return *((qreal *)data); } + + void setint(int v) { *((int *)data) = v; } + int getint() { return *((int *)data); } + + void setbool(bool v) { *((bool *)data) = v; } + bool getbool() { return *((bool *)data); } + + QVariant *getvariantptr() { return (QVariant *)typeDataPtr(); } + + void *typeDataPtr() { return (void *)&data; } + void *typeMemory() { return (void *)data; } + + void *data[2]; // Object stored here +}; + +struct Instr { + enum { + Init, // init + + Subscribe, // subscribe + SubscribeId, // subscribe + + LoadId, // load + LoadScope, // load + LoadRoot, // load + + ConvertIntToReal, // unaryop + ConvertRealToInt, // unaryop + + Real, // real_value + Int, // int_value + Bool, // bool_value + + AddReal, // binaryop + AddInt, // binaryop + + MinusReal, // binaryop + MinusInt, // binaryop + + GreaterThanReal, // binaryop + + NewString, // construct + CleanupString, // cleanup + + Fetch, // fetch + Store, // store + + Skip, // skip + + Done, + + // Speculative property resolution + InitString, // initstring + FindGeneric, // find + FindGenericTerminal, // find + FindProperty, // find + FindPropertyTerminal, // find + CleanupGeneric, // cleanup + ConvertGenericToReal, // genericunaryop + ConvertGenericToString, // genericunaryop + + } type; + + union { + struct { + int subscriptions; + int identifiers; + } init; + struct { + int offset; + int reg; + int index; + } subscribe; + struct { + int index; + int reg; + } load; + struct { + int output; + int index; + int reg; + } store; + struct { + int output; + int objectReg; + int index; + } fetch; + struct { + int reg; + } construct; + struct { + int reg; + qreal value; + } real_value; + struct { + int reg; + int value; + } int_value; + struct { + int reg; + bool value; + } bool_value; + struct { + int output; + int src1; + int src2; + } binaryop; + struct { + int output; + int src; + } unaryop; + struct { + int output; + int src; + int srcType; + } genericunaryop; + struct { + int reg; + int count; + } skip; + struct { + int reg; + int src; + int typeReg; + int name; + int subscribeIndex; + } find; + struct { + int reg; + int typeReg; + } cleanup; + struct { + int offset; + int dataIdx; + int length; + } initstring; + }; +}; + +struct Program { + int dataLength; + const char *data() const { return ((const char *)this) + sizeof(Program); } + const Instr *instructions() const { return (const Instr *)(data() + dataLength); } +}; +} + +struct QmlBindingCompiler +{ + struct Result { + Result() : unknownType(false), metaObject(0), type(-1), reg(-1) {} + bool operator==(const Result &o) const { + return unknownType == o.unknownType && + metaObject == o.metaObject && + type == o.type && + reg == o.reg; + } + bool operator!=(const Result &o) const { + return !(*this == o); + } + bool unknownType; + const QMetaObject *metaObject; + int type; + int reg; + }; + + QmlBindingCompiler() : registers(0), strings(0) {} + void reset(); + + QmlParser::Object *context; + QmlParser::Object *component; + QmlParser::Property *destination; + QHash ids; + + bool compile(QmlJS::AST::Node *); + + bool parseExpression(QmlJS::AST::Node *, Result &); + + bool tryName(QmlJS::AST::Node *); + bool parseName(QmlJS::AST::Node *, Result &); + + bool tryArith(QmlJS::AST::Node *); + bool parseArith(QmlJS::AST::Node *, Result &); + + bool tryLogic(QmlJS::AST::Node *); + bool parseLogic(QmlJS::AST::Node *, Result &); + + bool tryConditional(QmlJS::AST::Node *); + bool parseConditional(QmlJS::AST::Node *, Result &); + + bool tryConstant(QmlJS::AST::Node *); + bool parseConstant(QmlJS::AST::Node *, Result &); + + bool buildName(QStringList &, QmlJS::AST::Node *); + Result fetch(const QMetaObject *, int reg, int idx, const QStringList &); + + quint32 registers; + int acquireReg(); + void releaseReg(int); + + int registerString(const QString &); + int strings; + QByteArray data; + + QSet subscriptionSet; + bool subscription(const QStringList &); + QVector bytecode; +}; + +QByteArray QmlBindingVME::compile(const QmlBasicScript::Expression &expression) +{ + if (!expression.expression.asAST()) return false; + + QmlBindingCompiler bsc; + bsc.context = expression.context; + bsc.component = expression.component; + bsc.destination = expression.property; + bsc.ids = expression.ids; + + bool ok = bsc.compile(expression.expression.asAST()); + + if (ok) { + Program prog; + prog.dataLength = 4 * ((bsc.data.size() + 3) / 4); + int size = sizeof(Program) + bsc.bytecode.count() * sizeof(Instr); + size += prog.dataLength; + + QByteArray data; + data.resize(size); + memcpy(data.data(), &prog, sizeof(Program)); + if (prog.dataLength) + memcpy((char *)((Program *)data.data())->data(), bsc.data.constData(), + bsc.data.size()); + memcpy((char *)((Program *)data.data())->instructions(), bsc.bytecode.constData(), + bsc.bytecode.count() * sizeof(Instr)); + return data; + } else { + return QByteArray(); + } +} + +inline void subscribe(QObject *o, int notifyIndex, QmlBindingVME::Config::Subscription *s, + QmlBindingVME::Config *config) +{ + if (o != s->source || notifyIndex != s->notifyIndex) { + if (s->source) + QMetaObject::disconnect(s->source, s->notifyIndex, + config->target, config->targetSlot); + s->source = o; + s->notifyIndex = notifyIndex; + if (s->source && s->notifyIndex != -1) + QMetaObject::connect(s->source, s->notifyIndex, config->target, + config->targetSlot, Qt::DirectConnection); + } +} + +static QObject *variantToQObject(const QVariant &value, bool *ok) +{ + *ok = false; + Q_UNUSED(value); + return 0; +} + +static QmlPropertyCache::Data *findproperty(QObject *obj, + const QScriptDeclarativeClass::Identifier &name, + QmlEnginePrivate *enginePriv, + QmlPropertyCache::Data &local) +{ + QmlPropertyCache *cache = 0; + QmlDeclarativeData *ddata = QmlDeclarativeData::get(obj); + if (ddata) + cache = ddata->propertyCache; + if (!cache) { + cache = enginePriv->cache(obj); + if (cache && ddata) { cache->addref(); ddata->propertyCache = cache; } + } + + QmlPropertyCache::Data *property = 0; + + if (cache) { + property = cache->property(name); + } else { + qWarning() << "QmlBindingVME: Slow search" << enginePriv->objectClass->toString(name); + local = QmlPropertyCache::create(obj->metaObject(), + enginePriv->objectClass->toString(name)); + if (local.isValid()) + property = &local; + } + + return property; +} + +static bool findproperty(QObject *obj, + Register *output, Register *type, + QmlEnginePrivate *enginePriv, + QmlBindingVME::Config *config, int subIdx, + const QScriptDeclarativeClass::Identifier &name, + bool isTerminal) +{ + QmlPropertyCache::Data local; + QmlPropertyCache::Data *property = findproperty(obj, name, enginePriv, local); + + if (property) { + if (property->flags & QmlPropertyCache::Data::IsQObjectDerived) { + void *args[] = { output->typeDataPtr(), 0 }; + QMetaObject::metacall(obj, QMetaObject::ReadProperty, property->coreIndex, args); + type->setint(QMetaType::QObjectStar); + } else if (property->propType == qMetaTypeId()) { + QVariant v; + void *args[] = { &v, 0 }; + QMetaObject::metacall(obj, QMetaObject::ReadProperty, property->coreIndex, args); + + if (isTerminal) { + new (output->typeDataPtr()) QVariant(v); + type->setint(qMetaTypeId()); + } else { + bool ok; + output->setQObject(variantToQObject(v, &ok)); + if (!ok) return false; + type->setint(QMetaType::QObjectStar); + } + + } else { + if (!isTerminal) + return false; + + if (property->propType == QMetaType::QReal) { + void *args[] = { output->typeDataPtr(), 0 }; + QMetaObject::metacall(obj, QMetaObject::ReadProperty, property->coreIndex, args); + type->setint(QMetaType::QReal); + } else { + new (output->typeDataPtr()) + QVariant(obj->metaObject()->property(property->coreIndex).read(obj)); + type->setint(qMetaTypeId()); + } + + } + + if (subIdx != -1) + subscribe(obj, property->notifyIndex, config->subscriptions + subIdx, config); + + return true; + } else { + return false; + } +} + +static bool findproperty(Register *input, + Register *output, Register *type, + QmlEnginePrivate *enginePriv, + QmlBindingVME::Config *config, int subIdx, + const QScriptDeclarativeClass::Identifier &name, + bool isTerminal) +{ + return findproperty(input->getQObject(), output, type, enginePriv, + config, subIdx, name, isTerminal); +} + +static bool findgeneric(Register *output, // value output + Register *type, // value type output + QmlBindingVME::Config *config, + int subIdx, // Subscription index in config + QmlContextPrivate *context, // Context to search in + const QScriptDeclarativeClass::Identifier &name, + bool isTerminal) +{ + QmlEnginePrivate *enginePriv = QmlEnginePrivate::get(context->engine); + + while (context) { + + int contextPropertyIndex = context->propertyNames?context->propertyNames->value(name):-1; + if (contextPropertyIndex != -1) { + + if (contextPropertyIndex < context->idValueCount) { + output->setQObject(context->idValues[contextPropertyIndex]); + type->setint(QMetaType::QObjectStar); + } else { + const QVariant &value = context->propertyValues.at(contextPropertyIndex); + if (isTerminal) { + new (output->typeDataPtr()) QVariant(value); + type->setint(qMetaTypeId()); + } else { + bool ok; + output->setQObject(variantToQObject(value, &ok)); + if (!ok) return false; + type->setint(QMetaType::QObjectStar); + } + } + + return true; + } + + for (int ii = 0; ii < context->scripts.count(); ++ii) { + QScriptValue function = QScriptDeclarativeClass::function(context->scripts.at(ii), name); + if (function.isValid()) { + qFatal("Binding optimizer resolved name to QScript method"); + return false; + } + } + + if (QObject *root = context->defaultObjects.isEmpty()?0:context->defaultObjects.first()) { + + if (findproperty(root, output, type, enginePriv, config, subIdx, name, isTerminal)) + return true; + + } + + if (context->parent) { + context = QmlContextPrivate::get(context->parent); + } else { + context = 0; + } + } + + return false; +} + + +// Conversion functions +inline static qreal toReal(Register *reg, int type, bool *ok = 0) +{ + if (ok) *ok = true; + + if (type == QMetaType::QReal) { + return reg->getqreal(); + } else if (type == qMetaTypeId()) { + *ok = true; + return reg->getvariantptr()->toReal(); + } else { + if (ok) *ok = false; + return 0; + } +} + +inline static QString toString(Register *reg, int type, bool *ok = 0) +{ + if (ok) *ok = true; + + if (type == QMetaType::QReal) { + return QString::number(reg->getqreal()); + } else if (type == qMetaTypeId()) { + return reg->getvariantptr()->toString(); + } else { + if (ok) *ok = false; + return QString(); + } +} + +inline static bool toBool(Register *reg, int type, bool *ok = 0) +{ + if (ok) *ok = true; + + if (type == QMetaType::Bool) { + return reg->getbool(); + } else if (type == qMetaTypeId()) { + return reg->getvariantptr()->toBool(); + } else { + if (ok) *ok = false; + return false; + } +} + +inline static QUrl toUrl(Register *reg, int type, QmlContextPrivate *context, bool *ok = 0) +{ + if (ok) *ok = true; + + QUrl base; + if (type == qMetaTypeId()) { + QVariant *var = reg->getvariantptr(); + int vt = var->type(); + if (vt == QVariant::Url) { + base = var->toUrl(); + } else if (vt == QVariant::ByteArray) { + base = QUrl(QString::fromUtf8(var->toByteArray())); + } else if (vt == QVariant::String) { + base = QUrl(var->toString()); + } else { + if (ok) *ok = false; + return QUrl(); + } + } else { + if (ok) *ok = false; + return QUrl(); + } + + return QUrl(); +} + +void QmlBindingVME::run(const char *programData, Config *config, + QmlContextPrivate *context, + QObject **scopes, QObject **outputs) +{ + Register registers[32]; + int storeFlags = 0; + + QmlEnginePrivate *engine = QmlEnginePrivate::get(context->engine); + Program *program = (Program *)programData; + const Instr *instr = program->instructions(); + const char *data = program->data(); + + while (instr) { + + switch (instr->type) { + case Instr::Init: + if (!config->subscriptions && instr->init.subscriptions) + config->subscriptions = new Config::Subscription[instr->init.subscriptions]; + if (!config->identifiers && instr->init.identifiers) + config->identifiers = new QScriptDeclarativeClass::PersistentIdentifier[instr->init.identifiers]; + break; + + case Instr::SubscribeId: + case Instr::Subscribe: + { + QObject *o = registers[instr->subscribe.reg].getQObject(); + Config::Subscription *s = config->subscriptions + instr->subscribe.offset; + int notifyIndex = instr->subscribe.index; + + if (instr->type == Instr::SubscribeId) { + o = QmlContextPrivate::get(context); + notifyIndex += context->notifyIndex; + } + + subscribe(o, instr->subscribe.index, s, config); + } + break; + + case Instr::LoadId: + registers[instr->load.reg].setQObject(context->idValues[instr->load.index].data()); + break; + + case Instr::LoadScope: + registers[instr->load.reg].setQObject(scopes[instr->load.index]); + break; + + case Instr::LoadRoot: + registers[instr->load.reg].setQObject(context->defaultObjects.at(0)); + break; + + case Instr::ConvertIntToReal: + registers[instr->unaryop.output].setqreal(qreal(registers[instr->unaryop.src].getint())); + break; + + case Instr::ConvertRealToInt: + registers[instr->unaryop.output].setint(int(registers[instr->unaryop.src].getqreal())); + break; + + case Instr::Real: + registers[instr->real_value.reg].setqreal(instr->real_value.value); + break; + + case Instr::Int: + registers[instr->int_value.reg].setint(instr->int_value.value); + break; + + case Instr::Bool: + registers[instr->bool_value.reg].setbool(instr->bool_value.value); + break; + + case Instr::AddReal: + registers[instr->binaryop.output].setqreal(registers[instr->binaryop.src1].getqreal() + + registers[instr->binaryop.src2].getqreal()); + break; + + case Instr::AddInt: + registers[instr->binaryop.output].setint(registers[instr->binaryop.src1].getint() + + registers[instr->binaryop.src2].getint()); + break; + + case Instr::MinusReal: + registers[instr->binaryop.output].setqreal(registers[instr->binaryop.src1].getqreal() - + registers[instr->binaryop.src2].getqreal()); + break; + + case Instr::MinusInt: + registers[instr->binaryop.output].setint(registers[instr->binaryop.src1].getint() - + registers[instr->binaryop.src2].getint()); + break; + + case Instr::GreaterThanReal: + registers[instr->binaryop.output].setbool(registers[instr->binaryop.src1].getqreal() > + registers[instr->binaryop.src2].getqreal()); + break; + + case Instr::NewString: + new (registers[instr->construct.reg].typeMemory()) QString; + break; + + case Instr::CleanupString: + ((QString *)(registers[instr->cleanup.reg].typeDataPtr()))->~QString(); + break; + + case Instr::Fetch: + { + QObject *object = registers[instr->fetch.objectReg].getQObject(); + if (!object) { + qWarning() << "ERROR - Fetch"; + return; + } + void *argv[] = { registers[instr->fetch.output].typeDataPtr(), 0 }; + QMetaObject::metacall(object, QMetaObject::ReadProperty, instr->fetch.index, argv); + } + break; + + case Instr::Store: + { + int status = -1; + void *argv[] = { registers[instr->store.reg].typeDataPtr(), + 0, &status, &storeFlags }; + QMetaObject::metacall(outputs[instr->store.output], QMetaObject::WriteProperty, + instr->store.index, argv); + } + break; + + case Instr::Skip: + if (instr->skip.reg == -1 || !registers[instr->skip.reg].getbool()) + instr += instr->skip.count; + break; + + case Instr::Done: + return; + + case Instr::InitString: + if (!config->identifiers[instr->initstring.offset].identifier) { + QString str = QString::fromRawData((QChar *)(data + instr->initstring.dataIdx), + instr->initstring.length); + config->identifiers[instr->initstring.offset] = + engine->objectClass->createPersistentIdentifier(str); + } + break; + + case Instr::FindGenericTerminal: + case Instr::FindGeneric: + // We start the search in the parent context, as we know that the + // name is not present in the current context or it would have been + // found during the static compile + if (!findgeneric(registers + instr->find.reg, registers + instr->find.typeReg, + config, instr->find.subscribeIndex, QmlContextPrivate::get(context->parent), + config->identifiers[instr->find.name].identifier, + instr->type == Instr::FindGenericTerminal)) { + qWarning() << "ERROR - FindGeneric*"; + return; + } + break; + + case Instr::FindPropertyTerminal: + case Instr::FindProperty: + if (!findproperty(registers + instr->find.src, + registers + instr->find.reg, registers + instr->find.typeReg, + QmlEnginePrivate::get(context->engine), + config, instr->find.subscribeIndex, + config->identifiers[instr->find.name].identifier, + instr->type == Instr::FindPropertyTerminal)) { + qWarning() << "ERROR - FindProperty*"; + return; + } + break; + + case Instr::CleanupGeneric: + { + int type = registers[instr->cleanup.typeReg].getint(); + if (type == qMetaTypeId()) { + ((QVariant *)registers[instr->cleanup.reg].typeDataPtr())->~QVariant(); + } + } + break; + + case Instr::ConvertGenericToReal: + { + int type = registers[instr->genericunaryop.srcType].getint(); + registers[instr->genericunaryop.output].setqreal(toReal(registers + instr->genericunaryop.src, type)); + } + break; + + case Instr::ConvertGenericToString: + { + int type = registers[instr->genericunaryop.srcType].getint(); + void *regPtr = registers[instr->genericunaryop.output].typeDataPtr(); + new (regPtr) QString(toString(registers + instr->genericunaryop.src, type)); + } + break; + + default: + qFatal("EEK"); + break; + } + + instr++; + } +} + +void QmlBindingVME::dump(const QByteArray &programData) +{ + const Program *program = (const Program *)programData.constData(); + + int count = (programData.size() - sizeof(Program) - program->dataLength) / sizeof(Instr); + const Instr *instr = program->instructions(); + + while (count--) { + + switch (instr->type) { + case Instr::Init: + qWarning().nospace() << "Init" << "\t\t\t" << instr->init.subscriptions << "\t" << instr->init.identifiers; + break; + case Instr::Subscribe: + qWarning().nospace() << "Subscribe" << "\t\t" << instr->subscribe.offset << "\t" << instr->subscribe.reg << "\t" << instr->subscribe.index; + break; + case Instr::SubscribeId: + qWarning().nospace() << "SubscribeId" << "\t\t" << instr->subscribe.offset << "\t" << instr->subscribe.reg << "\t" << instr->subscribe.index; + break; + case Instr::LoadId: + qWarning().nospace() << "LoadId" << "\t\t\t" << instr->load.index << "\t" << instr->load.reg; + break; + case Instr::LoadScope: + qWarning().nospace() << "LoadScope" << "\t\t" << instr->load.index << "\t" << instr->load.reg; + break; + case Instr::LoadRoot: + qWarning().nospace() << "LoadRoot" << "\t\t" << instr->load.index << "\t" << instr->load.reg; + break; + case Instr::ConvertIntToReal: + qWarning().nospace() << "ConvertIntToReal" << "\t" << instr->unaryop.output << "\t" << instr->unaryop.src; + break; + case Instr::ConvertRealToInt: + qWarning().nospace() << "ConvertRealToInt" << "\t" << instr->unaryop.output << "\t" << instr->unaryop.src; + break; + case Instr::Real: + qWarning().nospace() << "Real" << "\t\t\t" << instr->real_value.reg << "\t" << instr->real_value.value; + break; + case Instr::Int: + qWarning().nospace() << "Int" << "\t\t\t" << instr->int_value.reg << "\t" << instr->int_value.value; + break; + case Instr::Bool: + qWarning().nospace() << "Bool" << "\t\t\t" << instr->bool_value.reg << "\t" << instr->bool_value.value; + break; + case Instr::AddReal: + qWarning().nospace() << "AddReal" << "\t\t\t" << instr->binaryop.output << "\t" << instr->binaryop.src1 << "\t" << instr->binaryop.src2; + break; + case Instr::AddInt: + qWarning().nospace() << "AddInt" << "\t\t\t" << instr->binaryop.output << "\t" << instr->binaryop.src1 << "\t" << instr->binaryop.src2; + break; + case Instr::MinusReal: + qWarning().nospace() << "MinusReal" << "\t\t" << instr->binaryop.output << "\t" << instr->binaryop.src1 << "\t" << instr->binaryop.src2; + break; + case Instr::MinusInt: + qWarning().nospace() << "MinusInt" << "\t\t" << instr->binaryop.output << "\t" << instr->binaryop.src1 << "\t" << instr->binaryop.src2; + break; + case Instr::GreaterThanReal: + qWarning().nospace() << "GreaterThanReal" << "\t\t" << instr->binaryop.output << "\t" << instr->binaryop.src1 << "\t" << instr->binaryop.src2; + break; + case Instr::NewString: + qWarning().nospace() << "NewString" << "\t\t" << instr->construct.reg; + break; + case Instr::CleanupString: + qWarning().nospace() << "CleanupString" << "\t\t" << instr->cleanup.reg << "\t" << instr->cleanup.typeReg; + break; + case Instr::Fetch: + qWarning().nospace() << "Fetch" << "\t\t\t" << instr->fetch.output << "\t" << instr->fetch.index << "\t" << instr->fetch.objectReg; + break; + case Instr::Store: + qWarning().nospace() << "Store" << "\t\t\t" << instr->store.output << "\t" << instr->store.index << "\t" << instr->store.reg; + break; + case Instr::Skip: + qWarning().nospace() << "Skip" << "\t\t\t" << instr->skip.reg << "\t" << instr->skip.count; + break; + case Instr::Done: + qWarning().nospace() << "Done"; + break; + case Instr::InitString: + qWarning().nospace() << "InitString" << "\t\t" << instr->initstring.offset << "\t" << instr->initstring.dataIdx << "\t" << instr->initstring.length; + break; + case Instr::FindGeneric: + qWarning().nospace() << "FindGeneric" << "\t\t" << instr->find.reg << "\t" << instr->find.typeReg << "\t" << instr->find.name; + break; + case Instr::FindGenericTerminal: + qWarning().nospace() << "FindGenericTerminal" << "\t" << instr->find.reg << "\t" << instr->find.typeReg << "\t" << instr->find.name; + break; + case Instr::FindProperty: + qWarning().nospace() << "FindProperty" << "\t\t" << instr->find.reg << "\t" << instr->find.src << "\t" << instr->find.typeReg << "\t" << instr->find.name; + break; + case Instr::FindPropertyTerminal: + qWarning().nospace() << "FindPropertyTerminal" << "\t\t" << instr->find.reg << "\t" << instr->find.src << "\t" << instr->find.typeReg << "\t" << instr->find.name; + break; + case Instr::CleanupGeneric: + qWarning().nospace() << "CleanupGeneric" << "\t\t" << instr->cleanup.reg << "\t" << instr->cleanup.typeReg; + break; + case Instr::ConvertGenericToReal: + qWarning().nospace() << "ConvertGenericToReal" << "\t" << instr->genericunaryop.output << "\t" << instr->genericunaryop.src << "\t" << instr->genericunaryop.srcType; + break; + case Instr::ConvertGenericToString: + qWarning().nospace() << "ConvertGenericToString" << "\t" << instr->genericunaryop.output << "\t" << instr->genericunaryop.src << "\t" << instr->genericunaryop.srcType; + break; + default: + qWarning().nospace() << "Unknown"; + break; + } + + ++instr; + } +} + +void QmlBindingCompiler::reset() +{ + registers = 0; + strings = 0; + data.clear(); + subscriptionSet.clear(); + bytecode.clear(); +} + +bool QmlBindingCompiler::compile(QmlJS::AST::Node *node) +{ + reset(); + + Result type; + + if (!parseExpression(node, type)) + return false; + + if (subscriptionSet.count() || strings) { + if (subscriptionSet.count() > 0xFFFF || + strings > 0xFFFF) + return false; + + Instr init; + init.type = Instr::Init; + init.init.subscriptions = subscriptionSet.count(); + init.init.identifiers = strings; + bytecode.prepend(init); + } + + if (type.unknownType) { + if (destination->type != QMetaType::QReal && + destination->type != QVariant::String) + return false; + + int convertReg = acquireReg(); + + if (destination->type == QMetaType::QReal) { + Instr convert; + convert.type = Instr::ConvertGenericToReal; + convert.genericunaryop.output = convertReg; + convert.genericunaryop.src = type.reg; + convert.genericunaryop.srcType = 2; // XXX + bytecode << convert; + } else if (destination->type == QVariant::String) { + Instr convert; + convert.type = Instr::ConvertGenericToString; + convert.genericunaryop.output = convertReg; + convert.genericunaryop.src = type.reg; + convert.genericunaryop.srcType = 2; // XXX + bytecode << convert; + } + + Instr cleanup; + cleanup.type = Instr::CleanupGeneric; + cleanup.cleanup.reg = type.reg; + cleanup.cleanup.typeReg = 2; // XXX + bytecode << cleanup; + + Instr instr; + instr.type = Instr::Store; + instr.store.output = 0; + instr.store.index = destination->index; + instr.store.reg = convertReg; + bytecode << instr; + + if (destination->type == QVariant::String) { + Instr cleanup; + cleanup.type = Instr::CleanupString; + cleanup.cleanup.reg = convertReg; + cleanup.cleanup.typeReg = -1; + bytecode << cleanup; + } + + releaseReg(convertReg); + + Instr done; + done.type = Instr::Done; + bytecode << done; + + return true; + } else { + // Can we store the final value? + if (type.type == QVariant::Int && + destination->type == QMetaType::QReal) { + Instr instr; + instr.type = Instr::ConvertIntToReal; + instr.unaryop.output = type.reg; + instr.unaryop.src = type.reg; + bytecode << instr; + type.type = QMetaType::QReal; + } else if (type.type == QMetaType::QReal && + destination->type == QVariant::Int) { + Instr instr; + instr.type = Instr::ConvertRealToInt; + instr.unaryop.output = type.reg; + instr.unaryop.src = type.reg; + bytecode << instr; + type.type = QVariant::Int; + } else if (type.type == destination->type) { + } else { + const QMetaObject *from = type.metaObject; + const QMetaObject *to = QmlMetaType::rawMetaObjectForType(destination->type); + + if (QmlMetaPropertyPrivate::canConvert(from, to)) + type.type = destination->type; + } + + if (type.type == destination->type) { + Instr instr; + instr.type = Instr::Store; + instr.store.output = 0; + instr.store.index = destination->index; + instr.store.reg = type.reg; + bytecode << instr; + + Instr done; + done.type = Instr::Done; + bytecode << done; + + return true; + } else { + return false; + } + } +} + +bool QmlBindingCompiler::parseExpression(QmlJS::AST::Node *node, Result &type) +{ + while (node->kind == AST::Node::Kind_NestedExpression) + node = static_cast(node)->expression; + + if (tryArith(node)) { + if (!parseArith(node, type)) return false; + } else if (tryLogic(node)) { + if (!parseLogic(node, type)) return false; + } else if (tryConditional(node)) { + if (!parseConditional(node, type)) return false; + } else if (tryName(node)) { + if (!parseName(node, type)) return false; + } else if (tryConstant(node)) { + if (!parseConstant(node, type)) return false; + } else { + return false; + } + return true; +} + +bool QmlBindingCompiler::tryName(QmlJS::AST::Node *node) +{ + return node->kind == AST::Node::Kind_IdentifierExpression || + node->kind == AST::Node::Kind_FieldMemberExpression; +} + +bool QmlBindingCompiler::parseName(AST::Node *node, Result &type) +{ + QStringList nameParts; + if (!buildName(nameParts, node)) + return false; + + int reg = acquireReg(); + if (reg == -1) + return false; + type.reg = reg; + + QmlParser::Object *absType = 0; + + QStringList subscribeName; + + for (int ii = 0; ii < nameParts.count(); ++ii) { + const QString &name = nameParts.at(ii); + + // We don't handle signal properties or attached properties + if (name.length() > 2 && name.startsWith(QLatin1String("on")) && + name.at(2).isUpper()) + return false; + if (name.at(0).isUpper()) + return false; + + + if (ii == 0) { + + if (ids.contains(name)) { + QmlParser::Object *idObject = ids.value(name); + absType = idObject; + type.metaObject = absType->metaObject(); + + // We check if the id object is the root or + // scope object to avoid a subscription + if (idObject == component) { + Instr instr; + instr.type = Instr::LoadRoot; + instr.load.index = 0; + instr.load.reg = reg; + bytecode << instr; + } else if (idObject == context) { + Instr instr; + instr.type = Instr::LoadScope; + instr.load.index = 0; + instr.load.reg = reg; + bytecode << instr; + } else { + Instr instr; + instr.type = Instr::LoadId; + instr.load.index = idObject->idIndex; + instr.load.reg = reg; + bytecode << instr; + + subscribeName << QLatin1String("$$$ID_") + name; + + if (subscription(subscribeName)) { + Instr sub; + sub.type = Instr::SubscribeId; + sub.subscribe.offset = subscriptionSet.count() - 1; + sub.subscribe.reg = reg; + sub.subscribe.index = instr.load.index; + bytecode << sub; + } + } + + } else { + + QByteArray utf8Name = name.toUtf8(); + const char *cname = utf8Name.constData(); + + int d0Idx = (context == component)?-1:context->metaObject()->indexOfProperty(cname); + int d1Idx = -1; + if (d0Idx == -1) + d1Idx = component->metaObject()->indexOfProperty(cname); + + if (d0Idx != -1) { + Instr instr; + instr.type = Instr::LoadScope; + instr.load.index = 0; + instr.load.reg = reg; + bytecode << instr; + + subscribeName << QLatin1String("$$$Scope"); + subscribeName << name; + + type = fetch(context->metaObject(), reg, d0Idx, subscribeName); + } else if(d1Idx != -1) { + Instr instr; + instr.type = Instr::LoadRoot; + instr.load.index = 0; + instr.load.reg = reg; + bytecode << instr; + + subscribeName << QLatin1String("$$$Root"); + subscribeName << name; + + type = fetch(component->metaObject(), reg, d1Idx, subscribeName); + } else { + Instr find; + if (nameParts.count() == 1) + find.type = Instr::FindGenericTerminal; + else + find.type = Instr::FindGeneric; + + find.find.reg = reg; + find.find.src = -1; + find.find.typeReg = 2; // XXX + find.find.name = registerString(name); + + subscribeName << QString(QLatin1String("$$$Generic_") + name); + if (subscription(subscribeName)) + find.find.subscribeIndex = subscriptionSet.count() - 1; + else + find.find.subscribeIndex = -1; + + bytecode << find; + type.unknownType = true; + } + + if (!type.unknownType && type.type == -1) + return false; // Couldn't fetch that type + } + + } else { + + const QMetaObject *mo = 0; + if (absType) + mo = absType->metaObject(); + else if (type.metaObject) + mo = type.metaObject; + + QByteArray utf8Name = name.toUtf8(); + const char *cname = utf8Name.constData(); + int idx = mo?mo->indexOfProperty(cname):-1; + if (absType && idx == -1) + return false; + + subscribeName << name; + + if (absType || (mo && mo->property(idx).isFinal())) { + absType = 0; + type = fetch(mo, reg, idx, subscribeName); + if (type.type == -1) + return false; + } else { + + Instr prop; + if (ii == nameParts.count() -1 ) + prop.type = Instr::FindPropertyTerminal; + else + prop.type = Instr::FindProperty; + + prop.find.reg = reg; + prop.find.src = reg; + prop.find.typeReg = 2; // XXX + prop.find.name = registerString(name); + if (subscription(subscribeName)) + prop.find.subscribeIndex = subscriptionSet.count() - 1; + else + prop.find.subscribeIndex = -1; + + type.unknownType = true; + type.metaObject = 0; + type.type = -1; + type.reg = reg; + bytecode << prop; + } + } + } + + return true; +} + +bool QmlBindingCompiler::tryArith(QmlJS::AST::Node *node) +{ + if (node->kind != AST::Node::Kind_BinaryExpression) + return false; + + AST::BinaryExpression *expression = static_cast(node); + if (expression->op == QSOperator::Add || + expression->op == QSOperator::Sub) + return true; + else + return false; +} + +bool QmlBindingCompiler::parseArith(QmlJS::AST::Node *node, Result &type) +{ + AST::BinaryExpression *expression = static_cast(node); + + Result lhs; + Result rhs; + + if (!parseExpression(expression->left, lhs)) return false; + if (!parseExpression(expression->right, rhs)) return false; + + if (lhs.type != QVariant::Int & + lhs.type != QMetaType::QReal) + return false; + + if (rhs.type != QVariant::Int & + rhs.type != QMetaType::QReal) + return false; + + bool nativeReal = rhs.type == QMetaType::QReal || + lhs.type == QMetaType::QReal; + + + if (nativeReal && lhs.type == QMetaType::Int) { + Instr convert; + convert.type = Instr::ConvertIntToReal; + convert.unaryop.output = lhs.reg; + convert.unaryop.src = lhs.reg; + bytecode << convert; + } + + if (nativeReal && rhs.type == QMetaType::Int) { + Instr convert; + convert.type = Instr::ConvertIntToReal; + convert.unaryop.output = rhs.reg; + convert.unaryop.src = rhs.reg; + bytecode << convert; + } + + Instr arith; + if (expression->op == QSOperator::Add) { + arith.type = nativeReal?Instr::AddReal:Instr::AddInt; + } else if (expression->op == QSOperator::Sub) { + arith.type = nativeReal?Instr::MinusReal:Instr::MinusInt; + } else { + qFatal("Unsupported arithmetic operator"); + } + + // We release early so we can reuse one of the registers as + // the output + releaseReg(lhs.reg); + releaseReg(rhs.reg); + + type.reg = acquireReg(); + + arith.binaryop.output = type.reg; + arith.binaryop.src1 = lhs.reg; + arith.binaryop.src2 = rhs.reg; + bytecode << arith; + + type.metaObject = 0; + type.type = nativeReal?QMetaType::QReal:QMetaType::Int; + + return true; +} + +bool QmlBindingCompiler::tryLogic(QmlJS::AST::Node *node) +{ + if (node->kind != AST::Node::Kind_BinaryExpression) + return false; + + AST::BinaryExpression *expression = static_cast(node); + if (expression->op == QSOperator::Gt) + return true; + else + return false; +} + +bool QmlBindingCompiler::parseLogic(QmlJS::AST::Node *node, Result &type) +{ + AST::BinaryExpression *expression = static_cast(node); + + Result lhs; + Result rhs; + + if (!parseExpression(expression->left, lhs)) return false; + if (!parseExpression(expression->right, rhs)) return false; + + if (lhs.type != QMetaType::QReal || rhs.type != QMetaType::QReal) + return false; + + // We release early so we can reuse one of the registers as + // the output + releaseReg(lhs.reg); + releaseReg(rhs.reg); + + type.reg = acquireReg(); + type.metaObject = 0; + type.type = QVariant::Bool; + + Instr op; + op.type = Instr::GreaterThanReal; + op.binaryop.output = type.reg; + op.binaryop.src1 = lhs.reg; + op.binaryop.src2 = rhs.reg; + bytecode << op; + + return true; +} + +bool QmlBindingCompiler::tryConditional(QmlJS::AST::Node *node) +{ + return (node->kind == AST::Node::Kind_ConditionalExpression); +} + +bool QmlBindingCompiler::parseConditional(QmlJS::AST::Node *node, Result &type) +{ + AST::ConditionalExpression *expression = static_cast(node); + + AST::Node *test = expression->expression; + if (test->kind == AST::Node::Kind_NestedExpression) + test = static_cast(test)->expression; + + Result etype; + if (!parseExpression(test, etype)) return false; + + if (etype.type != QVariant::Bool) + return false; + + Instr skip; + skip.type = Instr::Skip; + skip.skip.reg = etype.reg; + skip.skip.count = 0; + int skipIdx = bytecode.count(); + bytecode << skip; + + // Release to allow reuse of reg + releaseReg(etype.reg); + + int preConditionalSubscriptions = subscriptionSet.count(); + + Result ok; + if (!parseExpression(expression->ok, ok)) return false; + if (ok.unknownType) return false; + + int skipIdx2 = bytecode.count(); + skip.skip.reg = -1; + bytecode << skip; + + // Release to allow reuse of reg + releaseReg(ok.reg); + bytecode[skipIdx].skip.count = bytecode.count() - skipIdx - 1; + + Result ko; + if (!parseExpression(expression->ko, ko)) return false; + if (ko.unknownType) return false; + + // Release to allow reuse of reg + releaseReg(ko.reg); + bytecode[skipIdx2].skip.count = bytecode.count() - skipIdx2 - 1; + + if (ok != ko) + return false; // Must be same type and in same register + + if (preConditionalSubscriptions != subscriptionSet.count()) + return false; // Conditionals cannot introduce new subscriptions + + type = ok; + + return true; +} + +bool QmlBindingCompiler::tryConstant(QmlJS::AST::Node *node) +{ + return node->kind == AST::Node::Kind_TrueLiteral || + node->kind == AST::Node::Kind_FalseLiteral || + node->kind == AST::Node::Kind_NumericLiteral; +} + +bool QmlBindingCompiler::parseConstant(QmlJS::AST::Node *node, Result &type) +{ + type.metaObject = 0; + type.type = -1; + type.reg = acquireReg(); + + if (node->kind == AST::Node::Kind_TrueLiteral) { + type.type = QVariant::Bool; + Instr instr; + instr.type = Instr::Bool; + instr.bool_value.reg = type.reg; + instr.bool_value.value = true; + bytecode << instr; + return true; + } else if (node->kind == AST::Node::Kind_FalseLiteral) { + type.type = QVariant::Bool; + Instr instr; + instr.type = Instr::Bool; + instr.bool_value.reg = type.reg; + instr.bool_value.value = false; + bytecode << instr; + return true; + } else if (node->kind == AST::Node::Kind_NumericLiteral) { + type.type = QMetaType::QReal; + Instr instr; + instr.type = Instr::Real; + instr.real_value.reg = type.reg; + instr.real_value.value = qreal(static_cast(node)->value); + bytecode << instr; + return true; + } else { + return false; + } +} + +bool QmlBindingCompiler::buildName(QStringList &name, + QmlJS::AST::Node *node) +{ + if (node->kind == AST::Node::Kind_IdentifierExpression) { + name << static_cast(node)->name->asString(); + } else if (node->kind == AST::Node::Kind_FieldMemberExpression) { + AST::FieldMemberExpression *expr = + static_cast(node); + + if (!buildName(name, expr->base)) + return false; + + name << expr->name->asString(); + } else { + return false; + } + + return true; +} + +QmlBindingCompiler::Result +QmlBindingCompiler::fetch(const QMetaObject *mo, int reg, int idx, const QStringList &subName) +{ + QMetaProperty prop = mo->property(idx); + if (subscription(subName) && prop.hasNotifySignal() && prop.notifySignalIndex() != -1) { + Instr sub; + sub.type = Instr::Subscribe; + sub.subscribe.offset = subscriptionSet.count() - 1; + sub.subscribe.reg = reg; + sub.subscribe.index = prop.notifySignalIndex(); + bytecode << sub; + } + + Instr instr; + instr.type = Instr::Fetch; + instr.fetch.objectReg = reg; + instr.fetch.index = idx; + instr.fetch.output = reg; + bytecode << instr; + + Result rv; + rv.type = prop.userType(); + rv.metaObject = QmlMetaType::metaObjectForType(rv.type); + rv.reg = reg; + + if (!rv.metaObject && + rv.type != QMetaType::QReal && + rv.type != QMetaType::Int && + rv.type != QMetaType::Bool && + rv.type != qMetaTypeId()) + return Result(); // Unsupported type (string not supported yet); + + return rv; +} + +int QmlBindingCompiler::acquireReg() +{ + for (int ii = 0; ii < 32; ++ii) { + if (!(registers & (1 << ii))) { + registers |= (1 << ii); + return ii; + } + } + return -1; +} + +void QmlBindingCompiler::releaseReg(int reg) +{ + Q_ASSERT(reg >= 0 && reg <= 31); + + quint32 mask = 1 << reg; + registers &= ~mask; +} + +int QmlBindingCompiler::registerString(const QString &string) +{ + Q_ASSERT(!string.isEmpty()); + + // ### Padding? + QByteArray strdata((const char *)string.constData(), string.length() * sizeof(QChar)); + int rv = data.count(); + data += strdata; + + Instr reg; + reg.type = Instr::InitString; + reg.initstring.offset = strings++; + reg.initstring.dataIdx = rv; + reg.initstring.length = string.length(); + bytecode << reg; + + return strings - 1; +} + +bool QmlBindingCompiler::subscription(const QStringList &sub) +{ + QString str = sub.join(QLatin1String(".")); + + if (subscriptionSet.contains(str)) { + return false; + } else { + subscriptionSet.insert(str); + return true; + } +} + +QT_END_NAMESPACE + diff --git a/src/declarative/qml/qmlbindingvme_p.h b/src/declarative/qml/qmlbindingvme_p.h new file mode 100644 index 0000000..9a0239d --- /dev/null +++ b/src/declarative/qml/qmlbindingvme_p.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMLBINDINGVME_P_H +#define QMLBINDINGVME_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +class QObject; +class QmlContextPrivate; +class QmlBindingVME +{ +public: + struct Config { + Config() : target(0), targetSlot(-1), subscriptions(0), identifiers(0) {} + ~Config() { delete [] subscriptions; delete [] identifiers; } + QObject *target; + int targetSlot; + + struct Subscription { + QGuard source; + int notifyIndex; + }; + Subscription *subscriptions; + QScriptDeclarativeClass::PersistentIdentifier *identifiers; + }; + + static QByteArray compile(const QmlBasicScript::Expression &); + static void run(const char *program, + Config *config, QmlContextPrivate *context, + QObject **scopes, QObject **outputs); + static void dump(const QByteArray &); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QMLBINDINGVME_P_H + diff --git a/src/declarative/qml/qmlcompiler.cpp b/src/declarative/qml/qmlcompiler.cpp index f9a9b1b..fa7da95 100644 --- a/src/declarative/qml/qmlcompiler.cpp +++ b/src/declarative/qml/qmlcompiler.cpp @@ -64,9 +64,12 @@ #include "qmlscriptstring.h" #include "qmlglobal_p.h" #include "qmlscriptparser_p.h" +#include "qmlbinding.h" +#include "qmlbindingvme_p.h" #include +#include #include #include #include @@ -74,12 +77,12 @@ #include #include #include -#include QT_BEGIN_NAMESPACE DEFINE_BOOL_CONFIG_OPTION(compilerDump, QML_COMPILER_DUMP); DEFINE_BOOL_CONFIG_OPTION(compilerStatDump, QML_COMPILER_STATISTICS_DUMP); +DEFINE_BOOL_CONFIG_OPTION(qmlExperimental, QML_EXPERIMENTAL); using namespace QmlParser; @@ -2417,11 +2420,27 @@ void QmlCompiler::genBindingAssignment(QmlParser::Value *binding, Q_ASSERT(compileState.bindings.contains(binding)); const BindingReference &ref = compileState.bindings.value(binding); + if (ref.dataType == BindingReference::Experimental) { + QmlInstruction store; + store.type = QmlInstruction::StoreCompiledBinding; + store.assignBinding.value = output->indexForByteArray(ref.compiledData); + store.assignBinding.context = ref.bindingContext.stack; + store.assignBinding.owner = ref.bindingContext.owner; + if (valueTypeProperty) + store.assignBinding.property = (valueTypeProperty->index & 0xFFFF) | + ((valueTypeProperty->type & 0xFF)) << 16 | + ((prop->index & 0xFF) << 24); + else + store.assignBinding.property = prop->index; + store.line = binding->location.start.line; + output->bytecode << store; + return; + } QmlInstruction store; QmlBasicScript bs; - if (ref.isBasicScript) + if (ref.dataType == BindingReference::BasicScript) bs.load(ref.compiledData.constData() + sizeof(quint32)); if (bs.isSingleIdFetch()) { @@ -2519,16 +2538,31 @@ bool QmlCompiler::completeComponentBuild() expr.expression = binding.expression; bs.compile(expr); + + if (qmlExperimental() && (!bs.isValid() || (!bs.isSingleIdFetch() && !bs.isSingleContextProperty()))) { + + QByteArray qmvdata = QmlBindingVME::compile(expr); + if (!qmvdata.isEmpty()) { + qWarning() << expr.expression.asScript(); + QmlBindingVME::dump(qmvdata); + binding.dataType = BindingReference::Experimental; + binding.compiledData = qmvdata; + componentStat.optimizedBindings++; + continue; + } + } + quint32 type; if (bs.isValid()) { binding.compiledData = QByteArray(bs.compileData(), bs.compileDataSize()); type = QmlExpressionPrivate::BasicScriptEngineData; - binding.isBasicScript = true; + binding.dataType = BindingReference::BasicScript; componentStat.optimizedBindings++; } else { type = QmlExpressionPrivate::PreTransformedQtScriptData; + binding.dataType = BindingReference::QtScript; // Pre-rewrite the expression QString expression = binding.expression.asScript(); @@ -2557,7 +2591,6 @@ bool QmlCompiler::completeComponentBuild() QByteArray((const char *)&length, sizeof(quint32)) + QByteArray((const char *)expression.constData(), expression.length() * sizeof(QChar)); - binding.isBasicScript = false; componentStat.scriptBindings++; } diff --git a/src/declarative/qml/qmlcompiler_p.h b/src/declarative/qml/qmlcompiler_p.h index 809cc72..d734a12 100644 --- a/src/declarative/qml/qmlcompiler_p.h +++ b/src/declarative/qml/qmlcompiler_p.h @@ -269,7 +269,10 @@ private: QmlParser::Variant expression; QmlParser::Property *property; QmlParser::Value *value; - bool isBasicScript; + + enum DataType { QtScript, BasicScript, Experimental }; + DataType dataType; + QByteArray compiledData; BindingContext bindingContext; }; diff --git a/src/declarative/qml/qmlcontext_p.h b/src/declarative/qml/qmlcontext_p.h index 0229056..35971d9 100644 --- a/src/declarative/qml/qmlcontext_p.h +++ b/src/declarative/qml/qmlcontext_p.h @@ -130,7 +130,9 @@ public: static QmlContextPrivate *get(QmlContext *context) { return static_cast(QObjectPrivate::get(context)); } - + static QmlContext *get(QmlContextPrivate *context) { + return static_cast(context->q_func()); + } // Only used for debugging QList > instances; }; diff --git a/src/declarative/qml/qmlinstruction.cpp b/src/declarative/qml/qmlinstruction.cpp index 010010d..67db9c6 100644 --- a/src/declarative/qml/qmlinstruction.cpp +++ b/src/declarative/qml/qmlinstruction.cpp @@ -156,6 +156,9 @@ void QmlCompiledData::dump(QmlInstruction *instr, int idx) break; case QmlInstruction::StoreBinding: + qWarning().nospace() << idx << "\t\t" << line << "\t" << "STORE_BINDING\t" << instr->assignBinding.property << "\t" << instr->assignBinding.value << "\t" << instr->assignBinding.context; + break; + case QmlInstruction::StoreCompiledBinding: qWarning().nospace() << idx << "\t\t" << line << "\t" << "STORE_COMPILED_BINDING\t" << instr->assignBinding.property << "\t" << instr->assignBinding.value << "\t" << instr->assignBinding.context; break; case QmlInstruction::StoreIdOptBinding: diff --git a/src/declarative/qml/qmlinstruction_p.h b/src/declarative/qml/qmlinstruction_p.h index a5fc40c..d06ac86 100644 --- a/src/declarative/qml/qmlinstruction_p.h +++ b/src/declarative/qml/qmlinstruction_p.h @@ -128,6 +128,7 @@ public: AssignCustomType, /* assignCustomType */ StoreBinding, /* assignBinding */ + StoreCompiledBinding, /* assignBinding */ StoreIdOptBinding, /* assignIdOptBinding */ StoreObjPropBinding, /* assignObjPropBinding */ StoreValueSource, /* assignValueSource */ @@ -196,7 +197,7 @@ public: int castValue; } assignValueInterceptor; struct { - int property; + unsigned int property; int value; short context; short owner; diff --git a/src/declarative/qml/qmlvme.cpp b/src/declarative/qml/qmlvme.cpp index e3fc288..e4295a1 100644 --- a/src/declarative/qml/qmlvme.cpp +++ b/src/declarative/qml/qmlvme.cpp @@ -608,6 +608,27 @@ QObject *QmlVME::run(QmlVMEStack &stack, QmlContext *ctxt, } break; + case QmlInstruction::StoreCompiledBinding: + { + QObject *target = + stack.at(stack.count() - 1 - instr.assignBinding.owner); + QObject *scope = + stack.at(stack.count() - 1 - instr.assignBinding.context); + + int property = instr.assignBinding.property; + if (stack.count() == 1 && bindingSkipList.testBit(property & 0xFFFF)) + break; + + const char *data = datas.at(instr.assignBinding.value).constData(); + + QmlBinding_Basic *bind = + new QmlBinding_Basic(target, property, data, comp, scope, ctxt); + bindValues.append(bind); + bind->m_mePtr = &bindValues.values[bindValues.count - 1]; + bind->addToObject(target); + } + break; + case QmlInstruction::StoreIdOptBinding: { int coreIndex = instr.assignIdOptBinding.property; diff --git a/src/script/bridge/qscriptdeclarativeclass.cpp b/src/script/bridge/qscriptdeclarativeclass.cpp index df00ce9..98b3e26 100644 --- a/src/script/bridge/qscriptdeclarativeclass.cpp +++ b/src/script/bridge/qscriptdeclarativeclass.cpp @@ -36,6 +36,7 @@ QT_BEGIN_NAMESPACE QScriptDeclarativeClass::PersistentIdentifier::PersistentIdentifier() +: identifier(0) { new (&d) JSC::Identifier(); } -- cgit v0.12