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 <marc.mutz@qt.io>
This commit is contained in:
parent
d1e9cab81c
commit
06c2478762
@ -85,6 +85,221 @@ Q_LOGGING_CATEGORY(lcConnect, "qt.core.qobject.connect")
|
||||
|
||||
Q_CORE_EXPORT QBasicAtomicPointer<QSignalSpyCallbackSet> qt_signal_spy_callback_set = Q_BASIC_ATOMIC_INITIALIZER(nullptr);
|
||||
|
||||
// ConnectionList is a singly-linked list
|
||||
struct QObjectPrivate::ConnectionList
|
||||
{
|
||||
QAtomicPointer<Connection> first;
|
||||
QAtomicPointer<Connection> last;
|
||||
};
|
||||
static_assert(std::is_trivially_destructible_v<QObjectPrivate::ConnectionList>);
|
||||
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<quintptr>(c) & 1)
|
||||
return reinterpret_cast<SignalVector *>(reinterpret_cast<quintptr>(c) & ~quintptr(1u));
|
||||
return nullptr;
|
||||
}
|
||||
static Connection *fromSignalVector(SignalVector *v) {
|
||||
return reinterpret_cast<Connection *>(reinterpret_cast<quintptr>(v) | quintptr(1u));
|
||||
}
|
||||
};
|
||||
static_assert(std::is_trivial_v<QObjectPrivate::ConnectionOrSignalVector>);
|
||||
|
||||
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<Connection> nextConnectionList;
|
||||
Connection *prevConnectionList;
|
||||
|
||||
QObject *sender;
|
||||
QAtomicPointer<QObject> receiver;
|
||||
QAtomicPointer<QThreadData> receiverThreadData;
|
||||
union {
|
||||
StaticMetaCallFunction callFunction;
|
||||
QtPrivate::QSlotObjectBase *slotObj;
|
||||
};
|
||||
QAtomicPointer<const int> 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<ConnectionList *>(this + 1)[i + 1];
|
||||
}
|
||||
const ConnectionList &at(int i) const
|
||||
{
|
||||
return reinterpret_cast<const ConnectionList *>(this + 1)[i + 1];
|
||||
}
|
||||
int count() const { return static_cast<int>(allocated); }
|
||||
};
|
||||
static_assert(std::is_trivial_v<QObjectPrivate::SignalVector>); // 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<uint> currentConnectionId;
|
||||
QAtomicInt ref;
|
||||
QAtomicPointer<SignalVector> signalVector;
|
||||
Connection *senders = nullptr;
|
||||
Sender *currentSender = nullptr; // object currently activating the object
|
||||
QAtomicPointer<Connection> 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<SignalVector>);
|
||||
//static_assert(std::is_relocatable_v<ConnectionList>);
|
||||
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
|
||||
|
@ -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<quintptr>(c) & 1)
|
||||
return reinterpret_cast<SignalVector *>(reinterpret_cast<quintptr>(c) & ~quintptr(1u));
|
||||
return nullptr;
|
||||
}
|
||||
static Connection *fromSignalVector(SignalVector *v) {
|
||||
return reinterpret_cast<Connection *>(reinterpret_cast<quintptr>(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<Connection> nextConnectionList;
|
||||
Connection *prevConnectionList;
|
||||
|
||||
QObject *sender;
|
||||
QAtomicPointer<QObject> receiver;
|
||||
QAtomicPointer<QThreadData> receiverThreadData;
|
||||
union {
|
||||
StaticMetaCallFunction callFunction;
|
||||
QtPrivate::QSlotObjectBase *slotObj;
|
||||
};
|
||||
QAtomicPointer<const int> 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<Connection> first;
|
||||
QAtomicPointer<Connection> 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<ConnectionList *>(this + 1)[i + 1];
|
||||
}
|
||||
const ConnectionList &at(int i) const
|
||||
{
|
||||
return reinterpret_cast<const ConnectionList *>(this + 1)[i + 1];
|
||||
}
|
||||
int count() const { return static_cast<int>(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<uint> currentConnectionId;
|
||||
QAtomicInt ref;
|
||||
QAtomicPointer<SignalVector> signalVector;
|
||||
Connection *senders = nullptr;
|
||||
Sender *currentSender = nullptr; // object currently activating the object
|
||||
QAtomicPointer<Connection> 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<SignalVector>);
|
||||
//static_assert(std::is_relocatable_v<ConnectionList>);
|
||||
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<QtSharedPointer::ExternalRefCountData> 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
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user