Cache QObject method arguments
authorAaron Kennedy <aaron.kennedy@nokia.com>
Tue, 25 Oct 2011 14:41:33 +0000 (15:41 +0100)
committerQt by Nokia <qt-info@nokia.com>
Tue, 25 Oct 2011 14:55:57 +0000 (16:55 +0200)
This more than doubles the performance of invoking simple QObject methods
with parameters - such as myFunction(int,int) - multiple times.

Change-Id: I4bf21fb3980b09aedf0f440a246682c418933a65
Reviewed-by: Aaron Kennedy <aaron.kennedy@nokia.com>
src/declarative/qml/qdeclarativepropertycache.cpp
src/declarative/qml/qdeclarativepropertycache_p.h
src/declarative/qml/v8/qv8qobjectwrapper.cpp

index 134c588..6cb952e 100644 (file)
@@ -54,6 +54,13 @@ Q_DECLARE_METATYPE(QDeclarativeV8Handle);
 
 QT_BEGIN_NAMESPACE
 
+class QDeclarativePropertyCacheMethodArguments 
+{
+public:
+    QDeclarativePropertyCacheMethodArguments *next;
+    int arguments[0];
+};
+
 // Flags that do *NOT* depend on the property's QMetaProperty::userType() and thus are quick
 // to load
 static QDeclarativePropertyCache::Data::Flags fastFlagsForProperty(const QMetaProperty &p)
@@ -143,7 +150,7 @@ void QDeclarativePropertyCache::Data::load(const QMetaProperty &p, QDeclarativeE
 void QDeclarativePropertyCache::Data::load(const QMetaMethod &m)
 {
     coreIndex = m.methodIndex();
-    relatedIndex = -1;
+    arguments = 0;
     flags |= Data::IsFunction;
     if (m.methodType() == QMetaMethod::Signal)
         flags |= Data::IsSignal;
@@ -170,7 +177,7 @@ void QDeclarativePropertyCache::Data::load(const QMetaMethod &m)
 void QDeclarativePropertyCache::Data::lazyLoad(const QMetaMethod &m)
 {
     coreIndex = m.methodIndex();
-    relatedIndex = -1;
+    arguments = 0;
     flags |= Data::IsFunction;
     if (m.methodType() == QMetaMethod::Signal)
         flags |= Data::IsSignal;
@@ -200,7 +207,8 @@ void QDeclarativePropertyCache::Data::lazyLoad(const QMetaMethod &m)
 Creates a new empty QDeclarativePropertyCache.
 */
 QDeclarativePropertyCache::QDeclarativePropertyCache(QDeclarativeEngine *e)
-: engine(e), parent(0), propertyIndexCacheStart(0), methodIndexCacheStart(0)
+: engine(e), parent(0), propertyIndexCacheStart(0), methodIndexCacheStart(0), metaObject(0), 
+  argumentsCache(0)
 {
     Q_ASSERT(engine);
 }
@@ -209,7 +217,8 @@ QDeclarativePropertyCache::QDeclarativePropertyCache(QDeclarativeEngine *e)
 Creates a new QDeclarativePropertyCache of \a metaObject.
 */
 QDeclarativePropertyCache::QDeclarativePropertyCache(QDeclarativeEngine *e, const QMetaObject *metaObject)
-: engine(e), parent(0), propertyIndexCacheStart(0), methodIndexCacheStart(0)
+: engine(e), parent(0), propertyIndexCacheStart(0), methodIndexCacheStart(0), metaObject(0),
+  argumentsCache(0)
 {
     Q_ASSERT(engine);
     Q_ASSERT(metaObject);
@@ -221,6 +230,13 @@ QDeclarativePropertyCache::~QDeclarativePropertyCache()
 {
     clear();
 
+    QDeclarativePropertyCacheMethodArguments *args = argumentsCache;
+    while (args) {
+        QDeclarativePropertyCacheMethodArguments *next = args->next;
+        qFree(args);
+        args = next;
+    }
+
     if (parent) parent->release();
     parent = 0;
     engine = 0;
@@ -298,6 +314,7 @@ QDeclarativePropertyCache *QDeclarativePropertyCache::copy(int reserve)
     cache->methodIndexCacheStart = methodIndexCache.count() + methodIndexCacheStart;
     cache->stringCache.copyAndReserve(stringCache, reserve);
     cache->allowedRevisionCache = allowedRevisionCache;
+    cache->metaObject = metaObject;
 
     // We specifically do *NOT* copy the constructor
 
@@ -317,6 +334,8 @@ void QDeclarativePropertyCache::append(QDeclarativeEngine *engine, const QMetaOb
     Q_UNUSED(revision);
     Q_ASSERT(constructor.IsEmpty()); // We should not be appending to an in-use property cache
 
+    this->metaObject = metaObject;
+
     bool dynamicMetaObject = isDynamicMetaObject(metaObject);
 
     allowedRevisionCache.append(0);
@@ -404,8 +423,8 @@ void QDeclarativePropertyCache::append(QDeclarativeEngine *engine, const QMetaOb
 
         if (old) {
             // We only overload methods in the same class, exactly like C++
-            if (old->flags & Data::IsFunction && old->coreIndex >= methodOffset)
-                data->relatedIndex = old->coreIndex;
+            if (old->flags & Data::IsFunction && old->coreIndex >= methodOffset) 
+                data->flags |= Data::IsOverload;
             data->overrideIndexIsProperty = !bool(old->flags & Data::IsFunction);
             data->overrideIndex = old->coreIndex;
         }
@@ -588,6 +607,94 @@ QStringList QDeclarativePropertyCache::propertyNames() const
     return keys;
 }
 
+static int EnumType(const QMetaObject *meta, const QByteArray &str)
+{
+    QByteArray scope;
+    QByteArray name;
+    int scopeIdx = str.lastIndexOf("::");
+    if (scopeIdx != -1) {
+        scope = str.left(scopeIdx);
+        name = str.mid(scopeIdx + 2);
+    } else { 
+        name = str;
+    }
+    for (int i = meta->enumeratorCount() - 1; i >= 0; --i) {
+        QMetaEnum m = meta->enumerator(i);
+        if ((m.name() == name) && (scope.isEmpty() || (m.scope() == scope)))
+            return QVariant::Int;
+    }
+    return QVariant::Invalid;
+}
+
+// Returns an array of the arguments for method \a index.  The first entry in the array
+// is the number of arguments.
+int *QDeclarativePropertyCache::methodParameterTypes(QObject *object, int index, 
+                                                     QVarLengthArray<int, 9> &dummy,
+                                                     QByteArray *unknownTypeError)
+{
+    Q_ASSERT(object && index >= 0);
+
+    QDeclarativeData *ddata = QDeclarativeData::get(object, false);
+
+    if (ddata && ddata->propertyCache) {
+        typedef QDeclarativePropertyCacheMethodArguments A;
+
+        QDeclarativePropertyCache *c = ddata->propertyCache;
+        Q_ASSERT(index < c->methodIndexCacheStart + c->methodIndexCache.count());
+
+        while (index < c->methodIndexCacheStart)
+            c = c->parent;
+
+        Data *rv = const_cast<Data *>(&c->methodIndexCache.at(index - c->methodIndexCacheStart));
+
+        if (rv->arguments)  
+            return static_cast<A *>(rv->arguments)->arguments;
+
+        const QMetaObject *metaObject = object->metaObject();
+        QMetaMethod m = metaObject->method(index);
+        QList<QByteArray> argTypeNames = m.parameterTypes();
+
+        A *args = static_cast<A *>(qMalloc(sizeof(A) + (argTypeNames.count() + 1) * sizeof(int)));
+        args->arguments[0] = argTypeNames.count();
+
+        for (int ii = 0; ii < argTypeNames.count(); ++ii) {
+            int type = QMetaType::type(argTypeNames.at(ii));
+            if (type == QVariant::Invalid)
+                type = EnumType(object->metaObject(), argTypeNames.at(ii));
+            if (type == QVariant::Invalid) {
+                if (unknownTypeError) *unknownTypeError = argTypeNames.at(ii);
+                qFree(args);
+                return 0;
+            }
+            args->arguments[ii + 1] = type;
+        }
+
+        rv->arguments = args;
+        args->next = c->argumentsCache;
+        c->argumentsCache = args;
+        return static_cast<A *>(rv->arguments)->arguments;
+
+    } else {
+        QMetaMethod m = object->metaObject()->method(index);
+        QList<QByteArray> argTypeNames = m.parameterTypes();
+        dummy.resize(argTypeNames.count() + 1);
+        dummy[0] = argTypeNames.count();
+
+        for (int ii = 0; ii < argTypeNames.count(); ++ii) {
+            int type = QMetaType::type(argTypeNames.at(ii));
+            if (type == QVariant::Invalid)
+                type = EnumType(object->metaObject(), argTypeNames.at(ii));
+            if (type == QVariant::Invalid) {
+                if (unknownTypeError) *unknownTypeError = argTypeNames.at(ii);
+                return 0;
+            }
+            dummy[ii + 1] = type;
+        }
+
+        return dummy.data();
+    }
+}
+
 QDeclarativePropertyCache::Data *
 QDeclarativePropertyCache::property(QDeclarativeEngine *engine, QObject *obj, 
                                     const QHashedV8String &name, Data &local)
index 068db58..176d6b3 100644 (file)
@@ -58,6 +58,7 @@
 #include "qdeclarativenotifier_p.h"
 
 #include <private/qhashedstring_p.h>
+#include <QtCore/qvarlengtharray.h>
 #include <QtCore/qvector.h>
 
 QT_BEGIN_NAMESPACE
@@ -66,6 +67,7 @@ class QDeclarativeEngine;
 class QMetaProperty;
 class QV8Engine;
 class QV8QObjectWrapper;
+class QDeclarativePropertyCacheMethodArguments;
 
 class Q_DECLARATIVE_EXPORT QDeclarativePropertyCache : public QDeclarativeRefCount, public QDeclarativeCleanup
 {
@@ -109,9 +111,10 @@ public:
                     IsVMESignal        = 0x00040000, // Signal was added by QML
                     IsV8Function       = 0x00080000, // Function takes QDeclarativeV8Function* args
                     IsSignalHandler    = 0x00100000, // Function is a signal handler
+                    IsOverload         = 0x00200000, // Function is an overload of another function
 
                     // Internal QDeclarativePropertyCache flags
-                    NotFullyResolved   = 0x00200000  // True if the type data is to be lazily resolved
+                    NotFullyResolved   = 0x00400000  // True if the type data is to be lazily resolved
         };
         Q_DECLARE_FLAGS(Flags, Flag)
 
@@ -141,6 +144,7 @@ public:
         bool isVMESignal() const { return flags & IsVMESignal; }
         bool isV8Function() const { return flags & IsV8Function; }
         bool isSignalHandler() const { return flags & IsSignalHandler; }
+        bool isOverload() const { return flags & IsOverload; }
 
         union {
             int propType;             // When !NotFullyResolved
@@ -149,7 +153,7 @@ public:
         int coreIndex;
         union {
             int notifyIndex;  // When !IsFunction
-            int relatedIndex; // When IsFunction
+            void *arguments;  // When IsFunction && HasArguments
         };
         union {
             struct { // When !IsValueTypeVirtual
@@ -219,9 +223,10 @@ public:
     inline QDeclarativeEngine *qmlEngine() const;
     static Data *property(QDeclarativeEngine *, QObject *, const QString &, Data &);
     static Data *property(QDeclarativeEngine *, QObject *, const QHashedV8String &, Data &);
+    static int *methodParameterTypes(QObject *, int index, QVarLengthArray<int, 9> &dummy,
+                                     QByteArray *unknownTypeError);
 
     static bool isDynamicMetaObject(const QMetaObject *);
-
 protected:
     virtual void destroy();
     virtual void clear();
@@ -252,6 +257,9 @@ private:
     StringCache stringCache;
     AllowedRevisionCache allowedRevisionCache;
     v8::Persistent<v8::Function> constructor;
+
+    const QMetaObject *metaObject;
+    QDeclarativePropertyCacheMethodArguments *argumentsCache;
 };
 Q_DECLARE_OPERATORS_FOR_FLAGS(QDeclarativePropertyCache::Data::Flags);
   
index 1dc3db7..031cf83 100644 (file)
@@ -1173,20 +1173,17 @@ int QV8QObjectConnectionList::qt_metacall(QMetaObject::Call method, int index, v
 
         QList<Connection> connections = connectionList;
 
-        QMetaMethod method = data()->metaObject()->method(index);
-        Q_ASSERT(method.methodType() == QMetaMethod::Signal);
-        // XXX TODO: We should figure out a way to cache the parameter types to avoid resolving
-        // them each time.
-        QList<QByteArray> params = method.parameterTypes();
+        QVarLengthArray<int, 9> dummy;
+        int *argsTypes = QDeclarativePropertyCache::methodParameterTypes(data(), index, dummy, 0);
 
         v8::HandleScope handle_scope;
         v8::Context::Scope scope(engine->context());
 
-        QVarLengthArray<v8::Handle<v8::Value> > args(params.count());
-        int argCount = params.count();
+        int argCount = argsTypes?argsTypes[0]:0;
+        QVarLengthArray<v8::Handle<v8::Value>, 9> args(argCount);
 
         for (int ii = 0; ii < argCount; ++ii) {
-            int type = QMetaType::type(params.at(ii).constData());
+            int type = argsTypes[ii + 1];
             if (type == qMetaTypeId<QVariant>()) {
                 args[ii] = engine->fromVariant(*((QVariant *)metaArgs[ii + 1]));
             } else {
@@ -1452,34 +1449,13 @@ static v8::Handle<v8::Value> CallMethod(QObject *object, int index, int returnTy
     }
 }
 
-static int EnumType(const QMetaObject *meta, const QString &strname)
-{
-    QByteArray str = strname.toUtf8();
-    QByteArray scope;
-    QByteArray name;
-    int scopeIdx = str.lastIndexOf("::");
-    if (scopeIdx != -1) {
-        scope = str.left(scopeIdx);
-        name = str.mid(scopeIdx + 2);
-    } else { 
-        name = str;
-    }
-    for (int i = meta->enumeratorCount() - 1; i >= 0; --i) {
-        QMetaEnum m = meta->enumerator(i);
-        if ((m.name() == name) && (scope.isEmpty() || (m.scope() == scope)))
-            return QVariant::Int;
-    }
-    return QVariant::Invalid;
-}
-
 /*!
     Returns the match score for converting \a actual to be of type \a conversionType.  A 
     zero score means "perfect match" whereas a higher score is worse.
 
     The conversion table is copied out of the QtScript callQtMethod() function.
 */
-static int MatchScore(v8::Handle<v8::Value> actual, int conversionType, 
-                      const QByteArray &conversionTypeName)
+static int MatchScore(v8::Handle<v8::Value> actual, int conversionType)
 {
     if (actual->IsNumber()) {
         switch (conversionType) {
@@ -1551,11 +1527,13 @@ static int MatchScore(v8::Handle<v8::Value> actual, int conversionType,
         case QMetaType::VoidStar:
         case QMetaType::QObjectStar:
             return 0;
-        default:
-            if (!conversionTypeName.endsWith('*'))
-                return 10;
-            else
+        default: {
+            const char *typeName = QMetaType::typeName(conversionType);
+            if (typeName && typeName[strlen(typeName) - 1] == '*')
                 return 0;
+            else
+                return 10;
+        }
         }
     } else if (actual->IsObject()) {
         v8::Handle<v8::Object> obj = v8::Handle<v8::Object>::Cast(actual);
@@ -1615,28 +1593,32 @@ static const QDeclarativePropertyCache::Data * RelatedMethod(QObject *object,
                                                              QDeclarativePropertyCache::Data &dummy)
 {
     QDeclarativePropertyCache *cache = QDeclarativeData::get(object)->propertyCache;
-    if (current->relatedIndex == -1)
+    if (!current->isOverload())
         return 0;
 
+    Q_ASSERT(!current->overrideIndexIsProperty);
+
     if (cache) {
-        return cache->method(current->relatedIndex);
+        return cache->method(current->overrideIndex);
     } else {
         const QMetaObject *mo = object->metaObject();
         int methodOffset = mo->methodCount() - QMetaObject_methods(mo);
 
-        while (methodOffset > current->relatedIndex) {
+        while (methodOffset > current->overrideIndex) {
             mo = mo->superClass();
             methodOffset -= QMetaObject_methods(mo);
         }
 
-        QMetaMethod method = mo->method(current->relatedIndex);
+        QMetaMethod method = mo->method(current->overrideIndex);
         dummy.load(method);
         
         // Look for overloaded methods
         QByteArray methodName = QMetaMethod_name(method);
-        for (int ii = current->relatedIndex - 1; ii >= methodOffset; --ii) {
+        for (int ii = current->overrideIndex - 1; ii >= methodOffset; --ii) {
             if (methodName == QMetaMethod_name(mo->method(ii))) {
-                dummy.relatedIndex = ii;
+                dummy.setFlags(dummy.getFlags() | QDeclarativePropertyCache::Data::IsOverload);
+                dummy.overrideIndexIsProperty = 0;
+                dummy.overrideIndex = ii;
                 return &dummy;
             }
         }
@@ -1650,30 +1632,27 @@ static v8::Handle<v8::Value> CallPrecise(QObject *object, const QDeclarativeProp
 {
     if (data.hasArguments()) {
 
-        QMetaMethod m = object->metaObject()->method(data.coreIndex);
-        QList<QByteArray> argTypeNames = m.parameterTypes();
-        QVarLengthArray<int, 9> argTypes(argTypeNames.count());
-
-        // ### Cache
-        for (int ii = 0; ii < argTypeNames.count(); ++ii) {
-            argTypes[ii] = QMetaType::type(argTypeNames.at(ii));
-            if (argTypes[ii] == QVariant::Invalid) 
-                argTypes[ii] = EnumType(object->metaObject(), QString::fromLatin1(argTypeNames.at(ii)));
-            if (argTypes[ii] == QVariant::Invalid) {
-                QString error = QString::fromLatin1("Unknown method parameter type: %1").arg(QLatin1String(argTypeNames.at(ii)));
-                v8::ThrowException(v8::Exception::Error(engine->toString(error)));
-                return v8::Handle<v8::Value>();
-            }
+        int *args = 0;
+        QVarLengthArray<int, 9> dummy;
+        QByteArray unknownTypeError;
+
+        args = QDeclarativePropertyCache::methodParameterTypes(object, data.coreIndex, dummy, 
+                                                               &unknownTypeError);
+
+        if (!args) {
+            QString typeName = QString::fromLatin1(unknownTypeError);
+            QString error = QString::fromLatin1("Unknown method parameter type: %1").arg(typeName);
+            v8::ThrowException(v8::Exception::Error(engine->toString(error)));
+            return v8::Handle<v8::Value>();
         }
 
-        if (argTypes.count() > callArgs.Length()) {
+        if (args[0] > callArgs.Length()) {
             QString error = QLatin1String("Insufficient arguments");
             v8::ThrowException(v8::Exception::Error(engine->toString(error)));
             return v8::Handle<v8::Value>();
         }
 
-        return CallMethod(object, data.coreIndex, data.propType, argTypes.count(), 
-                          argTypes.data(), engine, callArgs);
+        return CallMethod(object, data.coreIndex, data.propType, args[0], args + 1, engine, callArgs);
 
     } else {
 
@@ -1708,12 +1687,18 @@ static v8::Handle<v8::Value> CallOverloaded(QObject *object, const QDeclarativeP
     const QDeclarativePropertyCache::Data *attempt = &data;
 
     do {
-        QList<QByteArray> methodArgTypeNames;
-
-        if (attempt->hasArguments())
-            methodArgTypeNames = object->metaObject()->method(attempt->coreIndex).parameterTypes();
+        QVarLengthArray<int, 9> dummy;
+        int methodArgumentCount = 0;
+        int *methodArgTypes = 0;
+        if (attempt->hasArguments()) {
+            typedef QDeclarativePropertyCache PC;
+            int *args = PC::methodParameterTypes(object, attempt->coreIndex, dummy, 0);
+            if (!args) // Must be an unknown argument
+                continue;
 
-        int methodArgumentCount = methodArgTypeNames.count();
+            methodArgumentCount = args[0];
+            methodArgTypes = args + 1;
+        }
 
         if (methodArgumentCount > argumentCount)
             continue; // We don't have sufficient arguments to call this method
@@ -1723,22 +1708,8 @@ static v8::Handle<v8::Value> CallOverloaded(QObject *object, const QDeclarativeP
             continue; // We already have a better option
 
         int methodMatchScore = 0;
-        QVarLengthArray<int, 9> methodArgTypes(methodArgumentCount);
-
-        bool unknownArgument = false;
-        for (int ii = 0; ii < methodArgumentCount; ++ii) {
-            methodArgTypes[ii] = QMetaType::type(methodArgTypeNames.at(ii));
-            if (methodArgTypes[ii] == QVariant::Invalid) 
-                methodArgTypes[ii] = EnumType(object->metaObject(), 
-                                              QString::fromLatin1(methodArgTypeNames.at(ii)));
-            if (methodArgTypes[ii] == QVariant::Invalid) {
-                unknownArgument = true;
-                break;
-            }
-            methodMatchScore += MatchScore(callArgs[ii], methodArgTypes[ii], methodArgTypeNames.at(ii));
-        }
-        if (unknownArgument)
-            continue; // We don't understand all the parameters
+        for (int ii = 0; ii < methodArgumentCount; ++ii) 
+            methodMatchScore += MatchScore(callArgs[ii], methodArgTypes[ii]);
 
         if (bestParameterScore > methodParameterScore || bestMatchScore > methodMatchScore) {
             best = attempt;
@@ -1887,7 +1858,7 @@ v8::Handle<v8::Value> QV8QObjectWrapper::Invoke(const v8::Arguments &args)
     }
 
     CallArgs callArgs(argCount, &arguments);
-    if (method.relatedIndex == -1) {
+    if (!method.isOverload()) {
         return CallPrecise(object, method, resource->engine, callArgs);
     } else {
         return CallOverloaded(object, method, resource->engine, callArgs);