From 06c2478762fafd16190259975bf381f14ad410e9 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Tue, 5 Apr 2022 11:21:04 -0700 Subject: [PATCH] QObjectPrivate: deinline a bunch of stuff only used in QtCore All of this connection management code is used only in QtCore and mustn't be touched by other modules (not even qtdeclarative). So there's no reason for it to be around and slow down the compilation time of everything using QObjectPrivate. Change-Id: If2e0f4b2190341ebaa31fffd16e31313f1b7020f Reviewed-by: Marc Mutz --- src/corelib/kernel/qobject.cpp | 224 +++++++++++++++++++++++++++++++++ src/corelib/kernel/qobject_p.h | 221 +------------------------------- 2 files changed, 229 insertions(+), 216 deletions(-) diff --git a/src/corelib/kernel/qobject.cpp b/src/corelib/kernel/qobject.cpp index 750f8b541c..78394b8649 100644 --- a/src/corelib/kernel/qobject.cpp +++ b/src/corelib/kernel/qobject.cpp @@ -85,6 +85,221 @@ Q_LOGGING_CATEGORY(lcConnect, "qt.core.qobject.connect") Q_CORE_EXPORT QBasicAtomicPointer qt_signal_spy_callback_set = Q_BASIC_ATOMIC_INITIALIZER(nullptr); +// ConnectionList is a singly-linked list +struct QObjectPrivate::ConnectionList +{ + QAtomicPointer first; + QAtomicPointer last; +}; +static_assert(std::is_trivially_destructible_v); +Q_DECLARE_TYPEINFO(QObjectPrivate::ConnectionList, Q_RELOCATABLE_TYPE); + +struct QObjectPrivate::ConnectionOrSignalVector +{ + union { + // linked list of orphaned connections that need cleaning up + ConnectionOrSignalVector *nextInOrphanList; + // linked list of connections connected to slots in this object + Connection *next; + }; + + static SignalVector *asSignalVector(ConnectionOrSignalVector *c) + { + if (reinterpret_cast(c) & 1) + return reinterpret_cast(reinterpret_cast(c) & ~quintptr(1u)); + return nullptr; + } + static Connection *fromSignalVector(SignalVector *v) { + return reinterpret_cast(reinterpret_cast(v) | quintptr(1u)); + } +}; +static_assert(std::is_trivial_v); + +struct QObjectPrivate::Connection : public ConnectionOrSignalVector +{ + // linked list of connections connected to slots in this object, next is in base class + Connection **prev; + // linked list of connections connected to signals in this object + QAtomicPointer nextConnectionList; + Connection *prevConnectionList; + + QObject *sender; + QAtomicPointer receiver; + QAtomicPointer receiverThreadData; + union { + StaticMetaCallFunction callFunction; + QtPrivate::QSlotObjectBase *slotObj; + }; + QAtomicPointer argumentTypes; + QAtomicInt ref_; + uint id = 0; + ushort method_offset; + ushort method_relative; + signed int signal_index : 27; // In signal range (see QObjectPrivate::signalIndex()) + ushort connectionType : 2; // 0 == auto, 1 == direct, 2 == queued, 3 == blocking + ushort isSlotObject : 1; + ushort ownArgumentTypes : 1; + ushort isSingleShot : 1; + Connection() : ref_(2), ownArgumentTypes(true) { + //ref_ is 2 for the use in the internal lists, and for the use in QMetaObject::Connection + } + ~Connection(); + int method() const { Q_ASSERT(!isSlotObject); return method_offset + method_relative; } + void ref() { ref_.ref(); } + void freeSlotObject() + { + if (isSlotObject) { + slotObj->destroyIfLastRef(); + isSlotObject = false; + } + } + void deref() + { + if (!ref_.deref()) { + Q_ASSERT(!receiver.loadRelaxed()); + Q_ASSERT(!isSlotObject); + delete this; + } + } +}; +Q_DECLARE_TYPEINFO(QObjectPrivate::Connection, Q_RELOCATABLE_TYPE); + +struct QObjectPrivate::SignalVector : public ConnectionOrSignalVector +{ + quintptr allocated; + // ConnectionList signals[] + ConnectionList &at(int i) + { + return reinterpret_cast(this + 1)[i + 1]; + } + const ConnectionList &at(int i) const + { + return reinterpret_cast(this + 1)[i + 1]; + } + int count() const { return static_cast(allocated); } +}; +static_assert(std::is_trivial_v); // it doesn't need to be, but it helps + +struct QObjectPrivate::ConnectionData +{ + // the id below is used to avoid activating new connections. When the object gets + // deleted it's set to 0, so that signal emission stops + QAtomicInteger currentConnectionId; + QAtomicInt ref; + QAtomicPointer signalVector; + Connection *senders = nullptr; + Sender *currentSender = nullptr; // object currently activating the object + QAtomicPointer orphaned; + + ~ConnectionData() + { + Q_ASSERT(ref.loadRelaxed() == 0); + auto *c = orphaned.fetchAndStoreRelaxed(nullptr); + if (c) + deleteOrphaned(c); + SignalVector *v = signalVector.loadRelaxed(); + if (v) { + v->~SignalVector(); + free(v); + } + } + + // must be called on the senders connection data + // assumes the senders and receivers lock are held + void removeConnection(Connection *c); + enum LockPolicy { + NeedToLock, + // Beware that we need to temporarily release the lock + // and thus calling code must carefully consider whether + // invariants still hold. + AlreadyLockedAndTemporarilyReleasingLock + }; + void cleanOrphanedConnections(QObject *sender, LockPolicy lockPolicy = NeedToLock) + { + if (orphaned.loadRelaxed() && ref.loadAcquire() == 1) + cleanOrphanedConnectionsImpl(sender, lockPolicy); + } + void cleanOrphanedConnectionsImpl(QObject *sender, LockPolicy lockPolicy); + + ConnectionList &connectionsForSignal(int signal) + { + return signalVector.loadRelaxed()->at(signal); + } + + void resizeSignalVector(uint size) + { + SignalVector *vector = this->signalVector.loadRelaxed(); + if (vector && vector->allocated > size) + return; + size = (size + 7) & ~7; + void *ptr = malloc(sizeof(SignalVector) + (size + 1) * sizeof(ConnectionList)); + auto newVector = new (ptr) SignalVector; + + int start = -1; + if (vector) { + // not (yet) existing trait: + //static_assert(std::is_relocatable_v); + //static_assert(std::is_relocatable_v); + memcpy(newVector, vector, sizeof(SignalVector) + (vector->allocated + 1) * sizeof(ConnectionList)); + start = vector->count(); + } + for (int i = start; i < int(size); ++i) + new (&newVector->at(i)) ConnectionList(); + newVector->next = nullptr; + newVector->allocated = size; + + signalVector.storeRelaxed(newVector); + if (vector) { + Connection *o = nullptr; + /* No ABA issue here: When adding a node, we only care about the list head, it doesn't + * matter if the tail changes. + */ + do { + o = orphaned.loadRelaxed(); + vector->nextInOrphanList = o; + } while (!orphaned.testAndSetRelease(o, ConnectionOrSignalVector::fromSignalVector(vector))); + + } + } + int signalVectorCount() const + { + return signalVector.loadAcquire() ? signalVector.loadRelaxed()->count() : -1; + } + + static void deleteOrphaned(ConnectionOrSignalVector *c); +}; + +struct QObjectPrivate::Sender +{ + Sender(QObject *receiver, QObject *sender, int signal) + : receiver(receiver), sender(sender), signal(signal) + { + if (receiver) { + ConnectionData *cd = receiver->d_func()->connections.loadRelaxed(); + previous = cd->currentSender; + cd->currentSender = this; + } + } + ~Sender() + { + if (receiver) + receiver->d_func()->connections.loadRelaxed()->currentSender = previous; + } + void receiverDeleted() + { + Sender *s = this; + while (s) { + s->receiver = nullptr; + s = s->previous; + } + } + Sender *previous; + QObject *receiver; + QObject *sender; + int signal; +}; +Q_DECLARE_TYPEINFO(QObjectPrivate::Sender, Q_RELOCATABLE_TYPE); + void qt_register_signal_spy_callbacks(QSignalSpyCallbackSet *callback_set) { qt_signal_spy_callback_set.storeRelease(callback_set); @@ -303,6 +518,15 @@ QObjectList QObjectPrivate::senderList() const return returnValue; } +void QObjectPrivate::ensureConnectionData() +{ + if (connections.loadRelaxed()) + return; + ConnectionData *cd = new ConnectionData; + cd->ref.ref(); + connections.storeRelaxed(cd); +} + /*! \internal Add the connection \a c to the list of connections of the sender's object diff --git a/src/corelib/kernel/qobject_p.h b/src/corelib/kernel/qobject_p.h index 4fcd2281e7..ed32a90497 100644 --- a/src/corelib/kernel/qobject_p.h +++ b/src/corelib/kernel/qobject_p.h @@ -135,123 +135,11 @@ public: typedef void (*StaticMetaCallFunction)(QObject *, QMetaObject::Call, int, void **); struct Connection; + struct ConnectionData; + struct ConnectionList; + struct ConnectionOrSignalVector; struct SignalVector; - - struct ConnectionOrSignalVector { - union { - // linked list of orphaned connections that need cleaning up - ConnectionOrSignalVector *nextInOrphanList; - // linked list of connections connected to slots in this object - Connection *next; - }; - - static SignalVector *asSignalVector(ConnectionOrSignalVector *c) - { - if (reinterpret_cast(c) & 1) - return reinterpret_cast(reinterpret_cast(c) & ~quintptr(1u)); - return nullptr; - } - static Connection *fromSignalVector(SignalVector *v) { - return reinterpret_cast(reinterpret_cast(v) | quintptr(1u)); - } - }; - - struct Connection : public ConnectionOrSignalVector - { - // linked list of connections connected to slots in this object, next is in base class - Connection **prev; - // linked list of connections connected to signals in this object - QAtomicPointer nextConnectionList; - Connection *prevConnectionList; - - QObject *sender; - QAtomicPointer receiver; - QAtomicPointer receiverThreadData; - union { - StaticMetaCallFunction callFunction; - QtPrivate::QSlotObjectBase *slotObj; - }; - QAtomicPointer argumentTypes; - QAtomicInt ref_; - uint id = 0; - ushort method_offset; - ushort method_relative; - signed int signal_index : 27; // In signal range (see QObjectPrivate::signalIndex()) - ushort connectionType : 2; // 0 == auto, 1 == direct, 2 == queued, 3 == blocking - ushort isSlotObject : 1; - ushort ownArgumentTypes : 1; - ushort isSingleShot : 1; - Connection() : ref_(2), ownArgumentTypes(true) { - //ref_ is 2 for the use in the internal lists, and for the use in QMetaObject::Connection - } - ~Connection(); - int method() const { Q_ASSERT(!isSlotObject); return method_offset + method_relative; } - void ref() { ref_.ref(); } - void freeSlotObject() - { - if (isSlotObject) { - slotObj->destroyIfLastRef(); - isSlotObject = false; - } - } - void deref() - { - if (!ref_.deref()) { - Q_ASSERT(!receiver.loadRelaxed()); - Q_ASSERT(!isSlotObject); - delete this; - } - } - }; - // ConnectionList is a singly-linked list - struct ConnectionList { - QAtomicPointer first; - QAtomicPointer last; - }; - - struct Sender - { - Sender(QObject *receiver, QObject *sender, int signal) - : receiver(receiver), sender(sender), signal(signal) - { - if (receiver) { - ConnectionData *cd = receiver->d_func()->connections.loadRelaxed(); - previous = cd->currentSender; - cd->currentSender = this; - } - } - ~Sender() - { - if (receiver) - receiver->d_func()->connections.loadRelaxed()->currentSender = previous; - } - void receiverDeleted() - { - Sender *s = this; - while (s) { - s->receiver = nullptr; - s = s->previous; - } - } - Sender *previous; - QObject *receiver; - QObject *sender; - int signal; - }; - - struct SignalVector : public ConnectionOrSignalVector { - quintptr allocated; - // ConnectionList signals[] - ConnectionList &at(int i) - { - return reinterpret_cast(this + 1)[i + 1]; - } - const ConnectionList &at(int i) const - { - return reinterpret_cast(this + 1)[i + 1]; - } - int count() const { return static_cast(allocated); } - }; + struct Sender; /* This contains the all connections from and to an object. @@ -267,93 +155,6 @@ public: to a slot in this object. The mutex of the receiver must be locked when touching the pointers of this linked list. */ - struct ConnectionData { - // the id below is used to avoid activating new connections. When the object gets - // deleted it's set to 0, so that signal emission stops - QAtomicInteger currentConnectionId; - QAtomicInt ref; - QAtomicPointer signalVector; - Connection *senders = nullptr; - Sender *currentSender = nullptr; // object currently activating the object - QAtomicPointer orphaned; - - ~ConnectionData() - { - Q_ASSERT(ref.loadRelaxed() == 0); - auto *c = orphaned.fetchAndStoreRelaxed(nullptr); - if (c) - deleteOrphaned(c); - SignalVector *v = signalVector.loadRelaxed(); - if (v) { - v->~SignalVector(); - free(v); - } - } - - // must be called on the senders connection data - // assumes the senders and receivers lock are held - void removeConnection(Connection *c); - enum LockPolicy { - NeedToLock, - // Beware that we need to temporarily release the lock - // and thus calling code must carefully consider whether - // invariants still hold. - AlreadyLockedAndTemporarilyReleasingLock - }; - void cleanOrphanedConnections(QObject *sender, LockPolicy lockPolicy = NeedToLock) - { - if (orphaned.loadRelaxed() && ref.loadAcquire() == 1) - cleanOrphanedConnectionsImpl(sender, lockPolicy); - } - void cleanOrphanedConnectionsImpl(QObject *sender, LockPolicy lockPolicy); - - ConnectionList &connectionsForSignal(int signal) - { - return signalVector.loadRelaxed()->at(signal); - } - - void resizeSignalVector(uint size) - { - SignalVector *vector = this->signalVector.loadRelaxed(); - if (vector && vector->allocated > size) - return; - size = (size + 7) & ~7; - void *ptr = malloc(sizeof(SignalVector) + (size + 1) * sizeof(ConnectionList)); - auto newVector = new (ptr) SignalVector; - - int start = -1; - if (vector) { - // not (yet) existing trait: - //static_assert(std::is_relocatable_v); - //static_assert(std::is_relocatable_v); - memcpy(newVector, vector, sizeof(SignalVector) + (vector->allocated + 1) * sizeof(ConnectionList)); - start = vector->count(); - } - for (int i = start; i < int(size); ++i) - new (&newVector->at(i)) ConnectionList(); - newVector->next = nullptr; - newVector->allocated = size; - - signalVector.storeRelaxed(newVector); - if (vector) { - Connection *o = nullptr; - /* No ABA issue here: When adding a node, we only care about the list head, it doesn't - * matter if the tail changes. - */ - do { - o = orphaned.loadRelaxed(); - vector->nextInOrphanList = o; - } while (!orphaned.testAndSetRelease(o, ConnectionOrSignalVector::fromSignalVector(vector))); - - } - } - int signalVectorCount() const - { - return signalVector.loadAcquire() ? signalVector.loadRelaxed()->count() : -1; - } - - static void deleteOrphaned(ConnectionOrSignalVector *c); - }; QObjectPrivate(int version = QObjectPrivateVersion); virtual ~QObjectPrivate(); @@ -412,14 +213,7 @@ public: void **slot); static bool disconnect(Connection *c); - void ensureConnectionData() - { - if (connections.loadRelaxed()) - return; - ConnectionData *cd = new ConnectionData; - cd->ref.ref(); - connections.storeRelaxed(cd); - } + void ensureConnectionData(); virtual std::string flagsForDumping() const; @@ -445,8 +239,6 @@ public: QAtomicPointer sharedRefcount; }; -Q_DECLARE_TYPEINFO(QObjectPrivate::ConnectionList, Q_RELOCATABLE_TYPE); - /* Catch mixing of incompatible library versions. @@ -587,9 +379,6 @@ bool QObjectPrivate::disconnect(const typename QtPrivate::FunctionPointer< Func1 &SignalType::Object::staticMetaObject); } -Q_DECLARE_TYPEINFO(QObjectPrivate::Connection, Q_RELOCATABLE_TYPE); -Q_DECLARE_TYPEINFO(QObjectPrivate::Sender, Q_RELOCATABLE_TYPE); - class QSemaphore; class Q_CORE_EXPORT QAbstractMetaCallEvent : public QEvent {