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