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 <bradley.hughes@nokia.com>
This commit is contained in:
parent
51048e1f31
commit
3d61c5ca8f
@ -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<QArrayData *>(&shared_empty);
|
||||
return sharable
|
||||
? const_cast<QArrayData *>(&shared_empty)
|
||||
: const_cast<QArrayData *>(&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);
|
||||
}
|
||||
|
||||
|
@ -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 <class T>
|
||||
@ -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<QTypedArrayData *>(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<QTypedArrayData *>(
|
||||
const_cast<QArrayData *>(&QArrayData::shared_empty));
|
||||
}
|
||||
|
||||
static QTypedArrayData *unsharableEmpty()
|
||||
{
|
||||
return static_cast<QTypedArrayData *>(
|
||||
const_cast<QArrayData *>(&QArrayData::unsharable_empty));
|
||||
}
|
||||
};
|
||||
|
||||
template <class T, size_t N>
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -111,6 +111,7 @@ public:
|
||||
{ return atomic.load(); }
|
||||
|
||||
void initializeOwned() { atomic.store(1); }
|
||||
void initializeUnsharable() { atomic.store(0); }
|
||||
|
||||
QBasicAtomicInt atomic;
|
||||
};
|
||||
|
@ -79,6 +79,8 @@ private slots:
|
||||
void typedData();
|
||||
void gccBug43247();
|
||||
void arrayOps();
|
||||
void setSharable_data();
|
||||
void setSharable();
|
||||
};
|
||||
|
||||
template <class T> const T &const_(const T &t) { return t; }
|
||||
@ -477,6 +479,7 @@ void tst_QArrayData::allocate_data()
|
||||
QTest::addColumn<size_t>("objectSize");
|
||||
QTest::addColumn<size_t>("alignment");
|
||||
QTest::addColumn<bool>("isCapacityReserved");
|
||||
QTest::addColumn<bool>("isSharable");
|
||||
QTest::addColumn<const QArrayData *>("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<int>)
|
||||
|
||||
static inline bool arrayIsFilledWith(const QArrayDataPointer<int> &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<QArrayDataPointer<int> >("array");
|
||||
QTest::addColumn<size_t>("size");
|
||||
QTest::addColumn<size_t>("capacity");
|
||||
QTest::addColumn<bool>("isCapacityReserved");
|
||||
QTest::addColumn<int>("fillValue");
|
||||
|
||||
QArrayDataPointer<int> null;
|
||||
QArrayDataPointer<int> empty; empty.clear();
|
||||
|
||||
static QStaticArrayData<int, 10> staticArrayData = {
|
||||
Q_STATIC_ARRAY_DATA_HEADER_INITIALIZER(int, 10),
|
||||
{ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 }
|
||||
};
|
||||
|
||||
QArrayDataPointer<int> emptyReserved(QTypedArrayData<int>::allocate(5, true, true));
|
||||
QArrayDataPointer<int> nonEmpty(QTypedArrayData<int>::allocate(10, false, true));
|
||||
QArrayDataPointer<int> nonEmptyReserved(QTypedArrayData<int>::allocate(15, true, true));
|
||||
QArrayDataPointer<int> staticArray(static_cast<QTypedArrayData<int> *>(&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<int>, 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<int> 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<int> 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<int> 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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user