mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-01 06:35:42 +00:00
420 lines
14 KiB
C++
420 lines
14 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#ifndef nsTArray_h__
|
|
# error "Don't include this file directly"
|
|
#endif
|
|
|
|
template<class Alloc>
|
|
nsTArray_base<Alloc>::nsTArray_base()
|
|
: mHdr(EmptyHdr()) {
|
|
MOZ_COUNT_CTOR(nsTArray_base);
|
|
}
|
|
|
|
template<class Alloc>
|
|
nsTArray_base<Alloc>::~nsTArray_base() {
|
|
if (mHdr != EmptyHdr() && !UsesAutoArrayBuffer()) {
|
|
Alloc::Free(mHdr);
|
|
}
|
|
MOZ_COUNT_DTOR(nsTArray_base);
|
|
}
|
|
|
|
template<class Alloc>
|
|
const nsTArrayHeader* nsTArray_base<Alloc>::GetAutoArrayBufferUnsafe(size_t elemAlign) const {
|
|
// Assuming |this| points to an nsAutoArray, we want to get a pointer to
|
|
// mAutoBuf. So just cast |this| to nsAutoArray* and read &mAutoBuf!
|
|
|
|
const void* autoBuf = &reinterpret_cast<const nsAutoArrayBase<nsTArray<uint32_t>, 1>*>(this)->mAutoBuf;
|
|
|
|
// If we're on a 32-bit system and elemAlign is 8, we need to adjust our
|
|
// pointer to take into account the extra alignment in the auto array.
|
|
|
|
MOZ_STATIC_ASSERT(sizeof(void*) != 4 ||
|
|
(MOZ_ALIGNOF(mozilla::AlignedElem<8>) == 8 &&
|
|
sizeof(nsAutoTArray<mozilla::AlignedElem<8>, 1>) ==
|
|
sizeof(void*) + sizeof(nsTArrayHeader) +
|
|
4 + sizeof(mozilla::AlignedElem<8>)),
|
|
"auto array padding wasn't what we expected");
|
|
|
|
// We don't support alignments greater than 8 bytes.
|
|
NS_ABORT_IF_FALSE(elemAlign <= 4 || elemAlign == 8, "unsupported alignment.");
|
|
if (sizeof(void*) == 4 && elemAlign == 8) {
|
|
autoBuf = reinterpret_cast<const char*>(autoBuf) + 4;
|
|
}
|
|
|
|
return reinterpret_cast<const Header*>(autoBuf);
|
|
}
|
|
|
|
template<class Alloc>
|
|
bool nsTArray_base<Alloc>::UsesAutoArrayBuffer() const {
|
|
if (!mHdr->mIsAutoArray) {
|
|
return false;
|
|
}
|
|
|
|
// This is nuts. If we were sane, we'd pass elemAlign as a parameter to
|
|
// this function. Unfortunately this function is called in nsTArray_base's
|
|
// destructor, at which point we don't know elem_type's alignment.
|
|
//
|
|
// We'll fall on our face and return true when we should say false if
|
|
//
|
|
// * we're not using our auto buffer,
|
|
// * elemAlign == 4, and
|
|
// * mHdr == GetAutoArrayBuffer(8).
|
|
//
|
|
// This could happen if |*this| lives on the heap and malloc allocated our
|
|
// buffer on the heap adjacent to |*this|.
|
|
//
|
|
// However, we can show that this can't happen. If |this| is an auto array
|
|
// (as we ensured at the beginning of the method), GetAutoArrayBuffer(8)
|
|
// always points to memory owned by |*this|, because (as we assert below)
|
|
//
|
|
// * GetAutoArrayBuffer(8) is at most 4 bytes past GetAutoArrayBuffer(4), and
|
|
// * sizeof(nsTArrayHeader) > 4.
|
|
//
|
|
// Since nsAutoTArray always contains an nsTArrayHeader,
|
|
// GetAutoArrayBuffer(8) will always point inside the auto array object,
|
|
// even if it doesn't point at the beginning of the header.
|
|
//
|
|
// Note that this means that we can't store elements with alignment 16 in an
|
|
// nsTArray, because GetAutoArrayBuffer(16) could lie outside the memory
|
|
// owned by this nsAutoTArray. We statically assert that elem_type's
|
|
// alignment is 8 bytes or less in nsAutoArrayBase.
|
|
|
|
MOZ_STATIC_ASSERT(sizeof(nsTArrayHeader) > 4,
|
|
"see comment above");
|
|
|
|
#ifdef DEBUG
|
|
ptrdiff_t diff = reinterpret_cast<const char*>(GetAutoArrayBuffer(8)) -
|
|
reinterpret_cast<const char*>(GetAutoArrayBuffer(4));
|
|
NS_ABORT_IF_FALSE(diff >= 0 && diff <= 4, "GetAutoArrayBuffer doesn't do what we expect.");
|
|
#endif
|
|
|
|
return mHdr == GetAutoArrayBuffer(4) || mHdr == GetAutoArrayBuffer(8);
|
|
}
|
|
|
|
|
|
template<class Alloc>
|
|
bool
|
|
nsTArray_base<Alloc>::EnsureCapacity(size_type capacity, size_type elemSize) {
|
|
// This should be the most common case so test this first
|
|
if (capacity <= mHdr->mCapacity)
|
|
return true;
|
|
|
|
// If the requested memory allocation exceeds size_type(-1)/2, then
|
|
// our doubling algorithm may not be able to allocate it.
|
|
// Additionally we couldn't fit in the Header::mCapacity
|
|
// member. Just bail out in cases like that. We don't want to be
|
|
// allocating 2 GB+ arrays anyway.
|
|
if ((uint64_t)capacity * elemSize > size_type(-1)/2) {
|
|
Alloc::SizeTooBig();
|
|
return false;
|
|
}
|
|
|
|
if (mHdr == EmptyHdr()) {
|
|
// Malloc() new data
|
|
Header *header = static_cast<Header*>
|
|
(Alloc::Malloc(sizeof(Header) + capacity * elemSize));
|
|
if (!header)
|
|
return false;
|
|
header->mLength = 0;
|
|
header->mCapacity = capacity;
|
|
header->mIsAutoArray = 0;
|
|
mHdr = header;
|
|
|
|
return true;
|
|
}
|
|
|
|
// We increase our capacity so |capacity * elemSize + sizeof(Header)| is the
|
|
// next power of two, if this value is less than pageSize bytes, or otherwise
|
|
// so it's the next multiple of pageSize.
|
|
const uint32_t pageSizeBytes = 12;
|
|
const uint32_t pageSize = 1 << pageSizeBytes;
|
|
|
|
uint32_t minBytes = capacity * elemSize + sizeof(Header);
|
|
uint32_t bytesToAlloc;
|
|
if (minBytes >= pageSize) {
|
|
// Round up to the next multiple of pageSize.
|
|
bytesToAlloc = pageSize * ((minBytes + pageSize - 1) / pageSize);
|
|
}
|
|
else {
|
|
// Round up to the next power of two. See
|
|
// http://graphics.stanford.edu/~seander/bithacks.html
|
|
bytesToAlloc = minBytes - 1;
|
|
bytesToAlloc |= bytesToAlloc >> 1;
|
|
bytesToAlloc |= bytesToAlloc >> 2;
|
|
bytesToAlloc |= bytesToAlloc >> 4;
|
|
bytesToAlloc |= bytesToAlloc >> 8;
|
|
bytesToAlloc |= bytesToAlloc >> 16;
|
|
bytesToAlloc++;
|
|
|
|
MOZ_ASSERT((bytesToAlloc & (bytesToAlloc - 1)) == 0,
|
|
"nsTArray's allocation size should be a power of two!");
|
|
}
|
|
|
|
Header *header;
|
|
if (UsesAutoArrayBuffer()) {
|
|
// Malloc() and copy
|
|
header = static_cast<Header*>(Alloc::Malloc(bytesToAlloc));
|
|
if (!header)
|
|
return false;
|
|
|
|
memcpy(header, mHdr, sizeof(Header) + Length() * elemSize);
|
|
} else {
|
|
// Realloc() existing data
|
|
header = static_cast<Header*>(Alloc::Realloc(mHdr, bytesToAlloc));
|
|
if (!header)
|
|
return false;
|
|
}
|
|
|
|
// How many elements can we fit in bytesToAlloc?
|
|
uint32_t newCapacity = (bytesToAlloc - sizeof(Header)) / elemSize;
|
|
MOZ_ASSERT(newCapacity >= capacity, "Didn't enlarge the array enough!");
|
|
header->mCapacity = newCapacity;
|
|
|
|
mHdr = header;
|
|
|
|
return true;
|
|
}
|
|
|
|
template<class Alloc>
|
|
void
|
|
nsTArray_base<Alloc>::ShrinkCapacity(size_type elemSize, size_t elemAlign) {
|
|
if (mHdr == EmptyHdr() || UsesAutoArrayBuffer())
|
|
return;
|
|
|
|
if (mHdr->mLength >= mHdr->mCapacity) // should never be greater than...
|
|
return;
|
|
|
|
size_type length = Length();
|
|
|
|
if (IsAutoArray() && GetAutoArrayBuffer(elemAlign)->mCapacity >= length) {
|
|
Header* header = GetAutoArrayBuffer(elemAlign);
|
|
|
|
// Copy data, but don't copy the header to avoid overwriting mCapacity
|
|
header->mLength = length;
|
|
memcpy(header + 1, mHdr + 1, length * elemSize);
|
|
|
|
Alloc::Free(mHdr);
|
|
mHdr = header;
|
|
return;
|
|
}
|
|
|
|
if (length == 0) {
|
|
MOZ_ASSERT(!IsAutoArray(), "autoarray should have fit 0 elements");
|
|
Alloc::Free(mHdr);
|
|
mHdr = EmptyHdr();
|
|
return;
|
|
}
|
|
|
|
size_type size = sizeof(Header) + length * elemSize;
|
|
void *ptr = Alloc::Realloc(mHdr, size);
|
|
if (!ptr)
|
|
return;
|
|
mHdr = static_cast<Header*>(ptr);
|
|
mHdr->mCapacity = length;
|
|
}
|
|
|
|
template<class Alloc>
|
|
void
|
|
nsTArray_base<Alloc>::ShiftData(index_type start,
|
|
size_type oldLen, size_type newLen,
|
|
size_type elemSize, size_t elemAlign) {
|
|
if (oldLen == newLen)
|
|
return;
|
|
|
|
// Determine how many elements need to be shifted
|
|
size_type num = mHdr->mLength - (start + oldLen);
|
|
|
|
// Compute the resulting length of the array
|
|
mHdr->mLength += newLen - oldLen;
|
|
if (mHdr->mLength == 0) {
|
|
ShrinkCapacity(elemSize, elemAlign);
|
|
} else {
|
|
// Maybe nothing needs to be shifted
|
|
if (num == 0)
|
|
return;
|
|
// Perform shift (change units to bytes first)
|
|
start *= elemSize;
|
|
newLen *= elemSize;
|
|
oldLen *= elemSize;
|
|
num *= elemSize;
|
|
char *base = reinterpret_cast<char*>(mHdr + 1) + start;
|
|
memmove(base + newLen, base + oldLen, num);
|
|
}
|
|
}
|
|
|
|
template<class Alloc>
|
|
bool
|
|
nsTArray_base<Alloc>::InsertSlotsAt(index_type index, size_type count,
|
|
size_type elementSize, size_t elemAlign) {
|
|
MOZ_ASSERT(index <= Length(), "Bogus insertion index");
|
|
size_type newLen = Length() + count;
|
|
|
|
EnsureCapacity(newLen, elementSize);
|
|
|
|
// Check for out of memory conditions
|
|
if (Capacity() < newLen)
|
|
return false;
|
|
|
|
// Move the existing elements as needed. Note that this will
|
|
// change our mLength, so no need to call IncrementLength.
|
|
ShiftData(index, 0, count, elementSize, elemAlign);
|
|
|
|
return true;
|
|
}
|
|
|
|
// nsTArray_base::IsAutoArrayRestorer is an RAII class which takes
|
|
// |nsTArray_base &array| in its constructor. When it's destructed, it ensures
|
|
// that
|
|
//
|
|
// * array.mIsAutoArray has the same value as it did when we started, and
|
|
// * if array has an auto buffer and mHdr would otherwise point to sEmptyHdr,
|
|
// array.mHdr points to array's auto buffer.
|
|
|
|
template<class Alloc>
|
|
nsTArray_base<Alloc>::IsAutoArrayRestorer::IsAutoArrayRestorer(
|
|
nsTArray_base<Alloc> &array,
|
|
size_t elemAlign)
|
|
: mArray(array),
|
|
mElemAlign(elemAlign),
|
|
mIsAuto(array.IsAutoArray())
|
|
{
|
|
}
|
|
|
|
template<class Alloc>
|
|
nsTArray_base<Alloc>::IsAutoArrayRestorer::~IsAutoArrayRestorer() {
|
|
// Careful: We don't want to set mIsAutoArray = 1 on sEmptyHdr.
|
|
if (mIsAuto && mArray.mHdr == mArray.EmptyHdr()) {
|
|
// Call GetAutoArrayBufferUnsafe() because GetAutoArrayBuffer() asserts
|
|
// that mHdr->mIsAutoArray is true, which surely isn't the case here.
|
|
mArray.mHdr = mArray.GetAutoArrayBufferUnsafe(mElemAlign);
|
|
mArray.mHdr->mLength = 0;
|
|
}
|
|
else {
|
|
mArray.mHdr->mIsAutoArray = mIsAuto;
|
|
}
|
|
}
|
|
|
|
template<class Alloc>
|
|
template<class Allocator>
|
|
bool
|
|
nsTArray_base<Alloc>::SwapArrayElements(nsTArray_base<Allocator>& other,
|
|
size_type elemSize,
|
|
size_t elemAlign) {
|
|
|
|
// EnsureNotUsingAutoArrayBuffer will set mHdr = sEmptyHdr even if we have an
|
|
// auto buffer. We need to point mHdr back to our auto buffer before we
|
|
// return, otherwise we'll forget that we have an auto buffer at all!
|
|
// IsAutoArrayRestorer takes care of this for us.
|
|
|
|
IsAutoArrayRestorer ourAutoRestorer(*this, elemAlign);
|
|
typename nsTArray_base<Allocator>::IsAutoArrayRestorer otherAutoRestorer(other, elemAlign);
|
|
|
|
// If neither array uses an auto buffer which is big enough to store the
|
|
// other array's elements, then ensure that both arrays use malloc'ed storage
|
|
// and swap their mHdr pointers.
|
|
if ((!UsesAutoArrayBuffer() || Capacity() < other.Length()) &&
|
|
(!other.UsesAutoArrayBuffer() || other.Capacity() < Length())) {
|
|
|
|
if (!EnsureNotUsingAutoArrayBuffer(elemSize) ||
|
|
!other.EnsureNotUsingAutoArrayBuffer(elemSize)) {
|
|
return false;
|
|
}
|
|
|
|
Header *temp = mHdr;
|
|
mHdr = other.mHdr;
|
|
other.mHdr = temp;
|
|
|
|
return true;
|
|
}
|
|
|
|
// Swap the two arrays using memcpy, since at least one is using an auto
|
|
// buffer which is large enough to hold all of the other's elements. We'll
|
|
// copy the shorter array into temporary storage.
|
|
//
|
|
// (We could do better than this in some circumstances. Suppose we're
|
|
// swapping arrays X and Y. X has space for 2 elements in its auto buffer,
|
|
// but currently has length 4, so it's using malloc'ed storage. Y has length
|
|
// 2. When we swap X and Y, we don't need to use a temporary buffer; we can
|
|
// write Y straight into X's auto buffer, write X's malloc'ed buffer on top
|
|
// of Y, and then switch X to using its auto buffer.)
|
|
|
|
if (!EnsureCapacity(other.Length(), elemSize) ||
|
|
!other.EnsureCapacity(Length(), elemSize)) {
|
|
return false;
|
|
}
|
|
|
|
// The EnsureCapacity calls above shouldn't have caused *both* arrays to
|
|
// switch from their auto buffers to malloc'ed space.
|
|
NS_ABORT_IF_FALSE(UsesAutoArrayBuffer() ||
|
|
other.UsesAutoArrayBuffer(),
|
|
"One of the arrays should be using its auto buffer.");
|
|
|
|
size_type smallerLength = NS_MIN(Length(), other.Length());
|
|
size_type largerLength = NS_MAX(Length(), other.Length());
|
|
void *smallerElements, *largerElements;
|
|
if (Length() <= other.Length()) {
|
|
smallerElements = Hdr() + 1;
|
|
largerElements = other.Hdr() + 1;
|
|
}
|
|
else {
|
|
smallerElements = other.Hdr() + 1;
|
|
largerElements = Hdr() + 1;
|
|
}
|
|
|
|
// Allocate temporary storage for the smaller of the two arrays. We want to
|
|
// allocate this space on the stack, if it's not too large. Sounds like a
|
|
// job for AutoTArray! (One of the two arrays we're swapping is using an
|
|
// auto buffer, so we're likely not allocating a lot of space here. But one
|
|
// could, in theory, allocate a huge AutoTArray on the heap.)
|
|
nsAutoTArray<uint8_t, 64, Alloc> temp;
|
|
if (!temp.SetCapacity(smallerLength * elemSize)) {
|
|
return false;
|
|
}
|
|
|
|
memcpy(temp.Elements(), smallerElements, smallerLength * elemSize);
|
|
memcpy(smallerElements, largerElements, largerLength * elemSize);
|
|
memcpy(largerElements, temp.Elements(), smallerLength * elemSize);
|
|
|
|
// Swap the arrays' lengths.
|
|
NS_ABORT_IF_FALSE((other.Length() == 0 || mHdr != EmptyHdr()) &&
|
|
(Length() == 0 || other.mHdr != EmptyHdr()),
|
|
"Don't set sEmptyHdr's length.");
|
|
size_type tempLength = Length();
|
|
mHdr->mLength = other.Length();
|
|
other.mHdr->mLength = tempLength;
|
|
|
|
return true;
|
|
}
|
|
|
|
template<class Alloc>
|
|
bool
|
|
nsTArray_base<Alloc>::EnsureNotUsingAutoArrayBuffer(size_type elemSize) {
|
|
if (UsesAutoArrayBuffer()) {
|
|
|
|
// If you call this on a 0-length array, we'll set that array's mHdr to
|
|
// sEmptyHdr, in flagrant violation of the nsAutoTArray invariants. It's
|
|
// up to you to set it back! (If you don't, the nsAutoTArray will forget
|
|
// that it has an auto buffer.)
|
|
if (Length() == 0) {
|
|
mHdr = EmptyHdr();
|
|
return true;
|
|
}
|
|
|
|
size_type size = sizeof(Header) + Length() * elemSize;
|
|
|
|
Header* header = static_cast<Header*>(Alloc::Malloc(size));
|
|
if (!header)
|
|
return false;
|
|
|
|
memcpy(header, mHdr, size);
|
|
header->mCapacity = Length();
|
|
mHdr = header;
|
|
}
|
|
|
|
return true;
|
|
}
|