From 3d61c5ca8f00b489435d5bea776b4a4c97c39793 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Abecasis?= Date: Thu, 24 Nov 2011 17:15:11 +0100 Subject: [PATCH] Add setSharable support in QArrayData stack Making use of the same feature added in RefCount. To keep with the intention of avoiding the allocation of "empty" array headers, this introduces an unsharable_empty, which allows users to maintain the "unsharable bit" on empty containers, without imposing any actual allocations. (Before anyone asks, there is no point to a zero-sized capacity-reserved container so no other combinations are needed for now.) Change-Id: Icaa40ac3100ad954fdc20dee0c991861136a5b19 Reviewed-by: Bradley T. Hughes --- src/corelib/tools/qarraydata.cpp | 12 +- src/corelib/tools/qarraydata.h | 15 +- src/corelib/tools/qarraydataops.h | 14 +- src/corelib/tools/qarraydatapointer.h | 20 ++- src/corelib/tools/qrefcount.h | 1 + .../tools/qarraydata/tst_qarraydata.cpp | 152 +++++++++++++++++- 6 files changed, 193 insertions(+), 21 deletions(-) diff --git a/src/corelib/tools/qarraydata.cpp b/src/corelib/tools/qarraydata.cpp index 1145ecb4f4d..c6e96c78a95 100644 --- a/src/corelib/tools/qarraydata.cpp +++ b/src/corelib/tools/qarraydata.cpp @@ -45,9 +45,10 @@ QT_BEGIN_NAMESPACE const QArrayData QArrayData::shared_null = { Q_REFCOUNT_INITIALIZE_STATIC, 0, 0, 0, 0 }; const QArrayData QArrayData::shared_empty = { Q_REFCOUNT_INITIALIZE_STATIC, 0, 0, 0, 0 }; +const QArrayData QArrayData::unsharable_empty = { { Q_BASIC_ATOMIC_INITIALIZER(0) }, 0, 0, 0, 0 }; QArrayData *QArrayData::allocate(size_t objectSize, size_t alignment, - size_t capacity, bool reserve) + size_t capacity, bool reserve, bool sharable) { // Alignment is a power of two Q_ASSERT(alignment >= Q_ALIGNOF(QArrayData) @@ -55,7 +56,9 @@ QArrayData *QArrayData::allocate(size_t objectSize, size_t alignment, // Don't allocate empty headers if (!capacity) - return const_cast(&shared_empty); + return sharable + ? const_cast(&shared_empty) + : const_cast(&unsharable_empty); // Allocate extra (alignment - Q_ALIGNOF(QArrayData)) padding bytes so we // can properly align the data array. This assumes malloc is able to @@ -69,7 +72,7 @@ QArrayData *QArrayData::allocate(size_t objectSize, size_t alignment, quintptr data = (quintptr(header) + sizeof(QArrayData) + alignment - 1) & ~(alignment - 1); - header->ref.initializeOwned(); + header->ref.atomic.store(sharable ? 1 : 0); header->size = 0; header->alloc = capacity; header->capacityReserved = reserve; @@ -87,6 +90,9 @@ void QArrayData::deallocate(QArrayData *data, size_t objectSize, && !(alignment & (alignment - 1))); Q_UNUSED(objectSize) Q_UNUSED(alignment) + if (data == &unsharable_empty) + return; + qFree(data); } diff --git a/src/corelib/tools/qarraydata.h b/src/corelib/tools/qarraydata.h index b7ab59257fc..1fd60e21554 100644 --- a/src/corelib/tools/qarraydata.h +++ b/src/corelib/tools/qarraydata.h @@ -74,12 +74,13 @@ struct Q_CORE_EXPORT QArrayData } static QArrayData *allocate(size_t objectSize, size_t alignment, - size_t capacity, bool reserve) Q_REQUIRED_RESULT; + size_t capacity, bool reserve, bool sharable) Q_REQUIRED_RESULT; static void deallocate(QArrayData *data, size_t objectSize, size_t alignment); static const QArrayData shared_null; static const QArrayData shared_empty; + static const QArrayData unsharable_empty; }; template @@ -99,11 +100,11 @@ struct QTypedArrayData class AlignmentDummy { QArrayData header; T data; }; - static QTypedArrayData *allocate(size_t capacity, bool reserve = false) - Q_REQUIRED_RESULT + static QTypedArrayData *allocate(size_t capacity, bool reserve = false, + bool sharable = true) Q_REQUIRED_RESULT { return static_cast(QArrayData::allocate(sizeof(T), - Q_ALIGNOF(AlignmentDummy), capacity, reserve)); + Q_ALIGNOF(AlignmentDummy), capacity, reserve, sharable)); } static void deallocate(QArrayData *data) @@ -122,6 +123,12 @@ struct QTypedArrayData return static_cast( const_cast(&QArrayData::shared_empty)); } + + static QTypedArrayData *unsharableEmpty() + { + return static_cast( + const_cast(&QArrayData::unsharable_empty)); + } }; template diff --git a/src/corelib/tools/qarraydataops.h b/src/corelib/tools/qarraydataops.h index e7c66a456b7..a3c372fe6b0 100644 --- a/src/corelib/tools/qarraydataops.h +++ b/src/corelib/tools/qarraydataops.h @@ -61,7 +61,7 @@ struct QPodArrayOps { void copyAppend(const T *b, const T *e) { - Q_ASSERT(this->ref == 1); + Q_ASSERT(!this->ref.isShared()); Q_ASSERT(b < e); Q_ASSERT(size_t(e - b) <= this->alloc - uint(this->size)); @@ -71,7 +71,7 @@ struct QPodArrayOps void copyAppend(size_t n, const T &t) { - Q_ASSERT(this->ref == 1); + Q_ASSERT(!this->ref.isShared()); Q_ASSERT(n <= this->alloc - uint(this->size)); T *iter = this->end(); @@ -91,7 +91,7 @@ struct QPodArrayOps void insert(T *where, const T *b, const T *e) { - Q_ASSERT(this->ref == 1); + Q_ASSERT(!this->ref.isShared()); Q_ASSERT(where >= this->begin() && where < this->end()); // Use copyAppend at end Q_ASSERT(b < e); Q_ASSERT(e <= where || b > this->end()); // No overlap @@ -109,7 +109,7 @@ struct QGenericArrayOps { void copyAppend(const T *b, const T *e) { - Q_ASSERT(this->ref == 1); + Q_ASSERT(!this->ref.isShared()); Q_ASSERT(b < e); Q_ASSERT(size_t(e - b) <= this->alloc - uint(this->size)); @@ -122,7 +122,7 @@ struct QGenericArrayOps void copyAppend(size_t n, const T &t) { - Q_ASSERT(this->ref == 1); + Q_ASSERT(!this->ref.isShared()); Q_ASSERT(n <= this->alloc - uint(this->size)); T *iter = this->end(); @@ -149,7 +149,7 @@ struct QGenericArrayOps void insert(T *where, const T *b, const T *e) { - Q_ASSERT(this->ref == 1); + Q_ASSERT(!this->ref.isShared()); Q_ASSERT(where >= this->begin() && where < this->end()); // Use copyAppend at end Q_ASSERT(b < e); Q_ASSERT(e <= where || b > this->end()); // No overlap @@ -222,7 +222,7 @@ struct QMovableArrayOps void insert(T *where, const T *b, const T *e) { - Q_ASSERT(this->ref == 1); + Q_ASSERT(!this->ref.isShared()); Q_ASSERT(where >= this->begin() && where < this->end()); // Use copyAppend at end Q_ASSERT(b < e); Q_ASSERT(e <= where || b > this->end()); // No overlap diff --git a/src/corelib/tools/qarraydatapointer.h b/src/corelib/tools/qarraydatapointer.h index f1cd1dc4b17..e42d146c58a 100644 --- a/src/corelib/tools/qarraydatapointer.h +++ b/src/corelib/tools/qarraydatapointer.h @@ -64,7 +64,9 @@ public: } QArrayDataPointer(const QArrayDataPointer &other) - : d((other.d->ref.ref(), other.d)) + : d(other.d->ref.ref() + ? other.d + : other.clone()) { } @@ -110,6 +112,22 @@ public: return d; } + void setSharable(bool sharable) + { + if (d->alloc == 0 && d->size == 0) { + Q_ASSERT(Data::sharedNull() == d + || Data::sharedEmpty() == d + || Data::unsharableEmpty() == d); + d = sharable + ? Data::sharedEmpty() + : Data::unsharableEmpty(); + return; + } + + detach(); + d->ref.setSharable(sharable); + } + void swap(QArrayDataPointer &other) { qSwap(d, other.d); diff --git a/src/corelib/tools/qrefcount.h b/src/corelib/tools/qrefcount.h index 9478ff1269f..bf864f2f58d 100644 --- a/src/corelib/tools/qrefcount.h +++ b/src/corelib/tools/qrefcount.h @@ -111,6 +111,7 @@ public: { return atomic.load(); } void initializeOwned() { atomic.store(1); } + void initializeUnsharable() { atomic.store(0); } QBasicAtomicInt atomic; }; diff --git a/tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp b/tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp index e9a32183310..e8edab20e49 100644 --- a/tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp +++ b/tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp @@ -79,6 +79,8 @@ private slots: void typedData(); void gccBug43247(); void arrayOps(); + void setSharable_data(); + void setSharable(); }; template const T &const_(const T &t) { return t; } @@ -477,6 +479,7 @@ void tst_QArrayData::allocate_data() QTest::addColumn("objectSize"); QTest::addColumn("alignment"); QTest::addColumn("isCapacityReserved"); + QTest::addColumn("isSharable"); QTest::addColumn("commonEmpty"); struct { @@ -492,10 +495,13 @@ void tst_QArrayData::allocate_data() struct { char const *description; bool isCapacityReserved; + bool isSharable; const QArrayData *commonEmpty; } options[] = { - { "Default", false, &QArrayData::shared_empty }, - { "Reserved", true, &QArrayData::shared_empty }, + { "Default", false, true, &QArrayData::shared_empty }, + { "Reserved", true, true, &QArrayData::shared_empty }, + { "Reserved | Unsharable", true, false, &QArrayData::unsharable_empty }, + { "Unsharable", false, false, &QArrayData::unsharable_empty }, }; for (size_t i = 0; i < sizeof(types)/sizeof(types[0]); ++i) @@ -505,7 +511,8 @@ void tst_QArrayData::allocate_data() + QLatin1String(": ") + QLatin1String(options[j].description))) << types[i].objectSize << types[i].alignment - << options[j].isCapacityReserved << options[j].commonEmpty; + << options[j].isCapacityReserved << options[j].isSharable + << options[j].commonEmpty; } void tst_QArrayData::allocate() @@ -513,6 +520,7 @@ void tst_QArrayData::allocate() QFETCH(size_t, objectSize); QFETCH(size_t, alignment); QFETCH(bool, isCapacityReserved); + QFETCH(bool, isSharable); QFETCH(const QArrayData *, commonEmpty); // Minimum alignment that can be requested is that of QArrayData. @@ -521,19 +529,20 @@ void tst_QArrayData::allocate() // Shared Empty QCOMPARE(QArrayData::allocate(objectSize, minAlignment, 0, - isCapacityReserved), commonEmpty); + isCapacityReserved, isSharable), commonEmpty); Deallocator keeper(objectSize, minAlignment); keeper.headers.reserve(1024); for (int capacity = 1; capacity <= 1024; capacity <<= 1) { QArrayData *data = QArrayData::allocate(objectSize, minAlignment, - capacity, isCapacityReserved); + capacity, isCapacityReserved, isSharable); keeper.headers.append(data); QCOMPARE(data->size, 0); QVERIFY(data->alloc >= uint(capacity)); QCOMPARE(data->capacityReserved, uint(isCapacityReserved)); + QCOMPARE(data->ref.isSharable(), isSharable); // Check that the allocated array can be used. Best tested with a // memory checker, such as valgrind, running. @@ -569,7 +578,7 @@ void tst_QArrayData::alignment() for (int i = 0; i < 100; ++i) { QArrayData *data = QArrayData::allocate(sizeof(Unaligned), - minAlignment, 8, false); + minAlignment, 8, false, true); keeper.headers.append(data); QVERIFY(data); @@ -903,5 +912,136 @@ void tst_QArrayData::arrayOps() } } +Q_DECLARE_METATYPE(QArrayDataPointer) + +static inline bool arrayIsFilledWith(const QArrayDataPointer &array, + int fillValue, size_t size) +{ + const int *iter = array->begin(); + const int *const end = array->end(); + + for (size_t i = 0; i < size; ++i, ++iter) + if (*iter != fillValue) + return false; + + if (iter != end) + return false; + + return true; +} + +void tst_QArrayData::setSharable_data() +{ + QTest::addColumn >("array"); + QTest::addColumn("size"); + QTest::addColumn("capacity"); + QTest::addColumn("isCapacityReserved"); + QTest::addColumn("fillValue"); + + QArrayDataPointer null; + QArrayDataPointer empty; empty.clear(); + + static QStaticArrayData staticArrayData = { + Q_STATIC_ARRAY_DATA_HEADER_INITIALIZER(int, 10), + { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 } + }; + + QArrayDataPointer emptyReserved(QTypedArrayData::allocate(5, true, true)); + QArrayDataPointer nonEmpty(QTypedArrayData::allocate(10, false, true)); + QArrayDataPointer nonEmptyReserved(QTypedArrayData::allocate(15, true, true)); + QArrayDataPointer staticArray(static_cast *>(&staticArrayData.header)); + + nonEmpty->copyAppend(5, 1); + nonEmptyReserved->copyAppend(7, 2); + + QTest::newRow("shared-null") << null << size_t(0) << size_t(0) << false << 0; + QTest::newRow("shared-empty") << empty << size_t(0) << size_t(0) << false << 0; + // unsharable-empty implicitly tested in shared-empty + QTest::newRow("empty-reserved") << emptyReserved << size_t(0) << size_t(5) << true << 0; + QTest::newRow("non-empty") << nonEmpty << size_t(5) << size_t(10) << false << 1; + QTest::newRow("non-empty-reserved") << nonEmptyReserved << size_t(7) << size_t(15) << true << 2; + QTest::newRow("static-array") << staticArray << size_t(10) << size_t(0) << false << 3; +} + +void tst_QArrayData::setSharable() +{ + QFETCH(QArrayDataPointer, array); + QFETCH(size_t, size); + QFETCH(size_t, capacity); + QFETCH(bool, isCapacityReserved); + QFETCH(int, fillValue); + + QVERIFY(array->ref.isShared()); // QTest has a copy + QVERIFY(array->ref.isSharable()); + + QCOMPARE(size_t(array->size), size); + QCOMPARE(size_t(array->alloc), capacity); + QCOMPARE(bool(array->capacityReserved), isCapacityReserved); + QVERIFY(arrayIsFilledWith(array, fillValue, size)); + + // shared-null becomes shared-empty, may otherwise detach + array.setSharable(true); + + QVERIFY(array->ref.isSharable()); + QVERIFY(arrayIsFilledWith(array, fillValue, size)); + + { + QArrayDataPointer copy(array); + QVERIFY(array->ref.isShared()); + QVERIFY(array->ref.isSharable()); + QCOMPARE(copy.data(), array.data()); + } + + // Unshare, must detach + array.setSharable(false); + + // Immutability (alloc == 0) is lost on detach + if (capacity == 0 && size != 0) + capacity = size; + + QVERIFY(!array->ref.isShared()); + QVERIFY(!array->ref.isSharable()); + + QCOMPARE(size_t(array->size), size); + QCOMPARE(size_t(array->alloc), capacity); + QCOMPARE(bool(array->capacityReserved), isCapacityReserved); + QVERIFY(arrayIsFilledWith(array, fillValue, size)); + + { + QArrayDataPointer copy(array); + QVERIFY(!array->ref.isShared()); + QVERIFY(!array->ref.isSharable()); + + // Null/empty is always shared + QCOMPARE(copy->ref.isShared(), !(size || isCapacityReserved)); + QVERIFY(copy->ref.isSharable()); + + QCOMPARE(size_t(copy->size), size); + QCOMPARE(size_t(copy->alloc), capacity); + QCOMPARE(bool(copy->capacityReserved), isCapacityReserved); + QVERIFY(arrayIsFilledWith(copy, fillValue, size)); + } + + // Make sharable, again + array.setSharable(true); + + QCOMPARE(array->ref.isShared(), !(size || isCapacityReserved)); + QVERIFY(array->ref.isSharable()); + + QCOMPARE(size_t(array->size), size); + QCOMPARE(size_t(array->alloc), capacity); + QCOMPARE(bool(array->capacityReserved), isCapacityReserved); + QVERIFY(arrayIsFilledWith(array, fillValue, size)); + + { + QArrayDataPointer copy(array); + QVERIFY(array->ref.isShared()); + QCOMPARE(copy.data(), array.data()); + } + + QCOMPARE(array->ref.isShared(), !(size || isCapacityReserved)); + QVERIFY(array->ref.isSharable()); +} + QTEST_APPLESS_MAIN(tst_QArrayData) #include "tst_qarraydata.moc"