From 82a8b494035ebf92804c851778a2edcbd511d53f Mon Sep 17 00:00:00 2001 From: Jon Coppeard Date: Tue, 18 Jun 2013 11:00:37 +0100 Subject: [PATCH] Bug 877762 - GC: Post-barrier cycle collector participants - 2 Stop nsTArray memmoving Heaps r=jlebar r=bz --- xpcom/glue/nsTArray-inl.h | 83 ++++++++-------- xpcom/glue/nsTArray.h | 192 +++++++++++++++++++++++++++++++++----- 2 files changed, 209 insertions(+), 66 deletions(-) diff --git a/xpcom/glue/nsTArray-inl.h b/xpcom/glue/nsTArray-inl.h index 9a1dd52e3e19..fb52f1aeeb09 100644 --- a/xpcom/glue/nsTArray-inl.h +++ b/xpcom/glue/nsTArray-inl.h @@ -8,22 +8,22 @@ # error "Don't include this file directly" #endif -template -nsTArray_base::nsTArray_base() +template +nsTArray_base::nsTArray_base() : mHdr(EmptyHdr()) { MOZ_COUNT_CTOR(nsTArray_base); } -template -nsTArray_base::~nsTArray_base() { +template +nsTArray_base::~nsTArray_base() { if (mHdr != EmptyHdr() && !UsesAutoArrayBuffer()) { Alloc::Free(mHdr); } MOZ_COUNT_DTOR(nsTArray_base); } -template -const nsTArrayHeader* nsTArray_base::GetAutoArrayBufferUnsafe(size_t elemAlign) const { +template +const nsTArrayHeader* nsTArray_base::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! @@ -48,8 +48,8 @@ const nsTArrayHeader* nsTArray_base::GetAutoArrayBufferUnsafe(size_t elem return reinterpret_cast(autoBuf); } -template -bool nsTArray_base::UsesAutoArrayBuffer() const { +template +bool nsTArray_base::UsesAutoArrayBuffer() const { if (!mHdr->mIsAutoArray) { return false; } @@ -96,9 +96,9 @@ bool nsTArray_base::UsesAutoArrayBuffer() const { } -template +template typename Alloc::ResultTypeProxy -nsTArray_base::EnsureCapacity(size_type capacity, size_type elemSize) { +nsTArray_base::EnsureCapacity(size_type capacity, size_type elemSize) { // This should be the most common case so test this first if (capacity <= mHdr->mCapacity) return Alloc::SuccessResult(); @@ -155,13 +155,16 @@ nsTArray_base::EnsureCapacity(size_type capacity, size_type elemSize) { } Header *header; - if (UsesAutoArrayBuffer()) { + if (UsesAutoArrayBuffer() || !Copy::allowRealloc) { // Malloc() and copy header = static_cast(Alloc::Malloc(bytesToAlloc)); if (!header) return Alloc::FailureResult(); - memcpy(header, mHdr, sizeof(Header) + Length() * elemSize); + Copy::CopyHeaderAndElements(header, mHdr, Length(), elemSize); + + if (!UsesAutoArrayBuffer()) + Alloc::Free(mHdr); } else { // Realloc() existing data header = static_cast(Alloc::Realloc(mHdr, bytesToAlloc)); @@ -179,9 +182,9 @@ nsTArray_base::EnsureCapacity(size_type capacity, size_type elemSize) { return Alloc::SuccessResult(); } -template +template void -nsTArray_base::ShrinkCapacity(size_type elemSize, size_t elemAlign) { +nsTArray_base::ShrinkCapacity(size_type elemSize, size_t elemAlign) { if (mHdr == EmptyHdr() || UsesAutoArrayBuffer()) return; @@ -195,7 +198,7 @@ nsTArray_base::ShrinkCapacity(size_type elemSize, size_t elemAlign) { // Copy data, but don't copy the header to avoid overwriting mCapacity header->mLength = length; - memcpy(header + 1, mHdr + 1, length * elemSize); + Copy::CopyElements(header + 1, mHdr + 1, length, elemSize); Alloc::Free(mHdr); mHdr = header; @@ -217,9 +220,9 @@ nsTArray_base::ShrinkCapacity(size_type elemSize, size_t elemAlign) { mHdr->mCapacity = length; } -template +template void -nsTArray_base::ShiftData(index_type start, +nsTArray_base::ShiftData(index_type start, size_type oldLen, size_type newLen, size_type elemSize, size_t elemAlign) { if (oldLen == newLen) @@ -240,15 +243,14 @@ nsTArray_base::ShiftData(index_type start, start *= elemSize; newLen *= elemSize; oldLen *= elemSize; - num *= elemSize; char *base = reinterpret_cast(mHdr + 1) + start; - memmove(base + newLen, base + oldLen, num); + Copy::MoveElements(base + newLen, base + oldLen, num, elemSize); } } -template +template bool -nsTArray_base::InsertSlotsAt(index_type index, size_type count, +nsTArray_base::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; @@ -262,7 +264,7 @@ nsTArray_base::InsertSlotsAt(index_type index, size_type count, // 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; } @@ -274,18 +276,18 @@ nsTArray_base::InsertSlotsAt(index_type index, size_type count, // * if array has an auto buffer and mHdr would otherwise point to sEmptyHdr, // array.mHdr points to array's auto buffer. -template -nsTArray_base::IsAutoArrayRestorer::IsAutoArrayRestorer( - nsTArray_base &array, - size_t elemAlign) +template +nsTArray_base::IsAutoArrayRestorer::IsAutoArrayRestorer( + nsTArray_base &array, + size_t elemAlign) : mArray(array), mElemAlign(elemAlign), mIsAuto(array.IsAutoArray()) { } -template -nsTArray_base::IsAutoArrayRestorer::~IsAutoArrayRestorer() { +template +nsTArray_base::IsAutoArrayRestorer::~IsAutoArrayRestorer() { // Careful: We don't want to set mIsAutoArray = 1 on sEmptyHdr. if (mIsAuto && mArray.mHdr == mArray.EmptyHdr()) { // Call GetAutoArrayBufferUnsafe() because GetAutoArrayBuffer() asserts @@ -298,12 +300,11 @@ nsTArray_base::IsAutoArrayRestorer::~IsAutoArrayRestorer() { } } -template +template template typename Alloc::ResultTypeProxy -nsTArray_base::SwapArrayElements(nsTArray_base& other, - size_type elemSize, - size_t elemAlign) { +nsTArray_base::SwapArrayElements(nsTArray_base& 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 @@ -311,7 +312,7 @@ nsTArray_base::SwapArrayElements(nsTArray_base& other, // IsAutoArrayRestorer takes care of this for us. IsAutoArrayRestorer ourAutoRestorer(*this, elemAlign); - typename nsTArray_base::IsAutoArrayRestorer otherAutoRestorer(other, elemAlign); + typename nsTArray_base::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 @@ -331,7 +332,7 @@ nsTArray_base::SwapArrayElements(nsTArray_base& other, return Alloc::SuccessResult(); } - // Swap the two arrays using memcpy, since at least one is using an auto + // Swap the two arrays by copying, 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. // @@ -375,9 +376,9 @@ nsTArray_base::SwapArrayElements(nsTArray_base& other, return Alloc::FailureResult(); } - memcpy(temp.Elements(), smallerElements, smallerLength * elemSize); - memcpy(smallerElements, largerElements, largerLength * elemSize); - memcpy(largerElements, temp.Elements(), smallerLength * elemSize); + Copy::CopyElements(temp.Elements(), smallerElements, smallerLength, elemSize); + Copy::CopyElements(smallerElements, largerElements, largerLength, elemSize); + Copy::CopyElements(largerElements, temp.Elements(), smallerLength, elemSize); // Swap the arrays' lengths. NS_ABORT_IF_FALSE((other.Length() == 0 || mHdr != EmptyHdr()) && @@ -390,9 +391,9 @@ nsTArray_base::SwapArrayElements(nsTArray_base& other, return Alloc::SuccessResult(); } -template +template bool -nsTArray_base::EnsureNotUsingAutoArrayBuffer(size_type elemSize) { +nsTArray_base::EnsureNotUsingAutoArrayBuffer(size_type elemSize) { if (UsesAutoArrayBuffer()) { // If you call this on a 0-length array, we'll set that array's mHdr to @@ -410,10 +411,10 @@ nsTArray_base::EnsureNotUsingAutoArrayBuffer(size_type elemSize) { if (!header) return false; - memcpy(header, mHdr, size); + Copy::CopyHeaderAndElements(header, mHdr, Length(), elemSize); header->mCapacity = Length(); mHdr = header; } - + return true; } diff --git a/xpcom/glue/nsTArray.h b/xpcom/glue/nsTArray.h index 73ebbb6b5a5e..3c059eb66b00 100644 --- a/xpcom/glue/nsTArray.h +++ b/xpcom/glue/nsTArray.h @@ -22,6 +22,11 @@ #include "nsTraceRefcnt.h" #include NEW_H +namespace JS { +template +class Heap; +} /* namespace JS */ + // // nsTArray is a resizable array class, like std::vector. // @@ -56,6 +61,9 @@ // T MAY define operator< for sorting. // T MAY define operator== for searching. // +// (Note that the memmove requirement may be relaxed for certain types - see +// nsTArray_CopyElements below.) +// // For methods taking a Comparator instance, the Comparator must be a class // defining the following methods: // @@ -342,13 +350,13 @@ struct nsTArray_SafeElementAtHelper, Derived> : // directly. It holds common implementation code that does not depend on the // element type of the nsTArray. // -template +template class nsTArray_base { // Allow swapping elements with |nsTArray_base|s created using a // different allocator. This is kosher because all allocators use // the same free(). - template + template friend class nsTArray_base; protected: @@ -396,7 +404,7 @@ protected: // @param elemSize The size of an array element. // @param elemAlign The alignment in bytes of an array element. void ShrinkCapacity(size_type elemSize, size_t elemAlign); - + // This method may be called to resize a "gap" in the array by shifting // elements around. It updates mLength appropriately. If the resulting // array has zero elements, then the array's memory is free'd. @@ -435,18 +443,18 @@ protected: protected: template typename Alloc::ResultTypeProxy - SwapArrayElements(nsTArray_base& other, + SwapArrayElements(nsTArray_base& other, size_type elemSize, size_t elemAlign); // This is an RAII class used in SwapArrayElements. class IsAutoArrayRestorer { public: - IsAutoArrayRestorer(nsTArray_base &array, size_t elemAlign); + IsAutoArrayRestorer(nsTArray_base &array, size_t elemAlign); ~IsAutoArrayRestorer(); private: - nsTArray_base &mArray; + nsTArray_base &mArray; size_t mElemAlign; bool mIsAuto; }; @@ -473,7 +481,7 @@ protected: // Returns a Header for the built-in buffer of this nsAutoTArray, but doesn't // assert that we are an nsAutoTArray. Header* GetAutoArrayBufferUnsafe(size_t elemAlign) { - return const_cast(static_cast*>(this)-> + return const_cast(static_cast*>(this)-> GetAutoArrayBufferUnsafe(elemAlign)); } const Header* GetAutoArrayBufferUnsafe(size_t elemAlign) const; @@ -486,7 +494,7 @@ protected: // null. If the array is empty, then this will point to sEmptyHdr. Header *mHdr; - Header* Hdr() const { + Header* Hdr() const { return mHdr; } @@ -563,6 +571,139 @@ struct AssignRangeAlgorithm { } }; +// +// Normally elements are copied with memcpy and memmove, but for some element +// types that is problematic. The nsTArray_CopyElements template class can be +// specialized to ensure that copying calls constructors and destructors +// instead, as is done below for JS::Heap elements. +// + +// +// A class that defines how to copy elements using memcpy/memmove. +// +struct nsTArray_CopyWithMemutils +{ + const static bool allowRealloc = true; + + static void CopyElements(void* dest, const void* src, size_t count, size_t elemSize) { + memcpy(dest, src, count * elemSize); + } + + static void CopyHeaderAndElements(void* dest, const void* src, size_t count, size_t elemSize) { + memcpy(dest, src, sizeof(nsTArrayHeader) + count * elemSize); + } + + static void MoveElements(void* dest, const void* src, size_t count, size_t elemSize) { + memmove(dest, src, count * elemSize); + } +}; + +// +// A template class that defines how to copy elements calling their constructors +// and destructors appropriately. +// +template +struct nsTArray_CopyWithConstructors +{ + typedef nsTArrayElementTraits traits; + + const static bool allowRealloc = false; + + static void CopyElements(void* dest, void* src, size_t count, size_t elemSize) { + ElemType* destElem = static_cast(dest); + ElemType* srcElem = static_cast(src); + ElemType* destElemEnd = destElem + count; +#ifdef DEBUG + ElemType* srcElemEnd = srcElem + count; + MOZ_ASSERT(srcElemEnd <= destElem || srcElemEnd > destElemEnd); +#endif + while (destElem != destElemEnd) { + traits::Construct(destElem, *srcElem); + traits::Destruct(srcElem); + ++destElem; + ++srcElem; + } + } + + static void CopyHeaderAndElements(void* dest, void* src, size_t count, size_t elemSize) { + nsTArrayHeader* destHeader = static_cast(dest); + nsTArrayHeader* srcHeader = static_cast(src); + *destHeader = *srcHeader; + CopyElements(static_cast(dest) + sizeof(nsTArrayHeader), + static_cast(src) + sizeof(nsTArrayHeader), + count, elemSize); + } + + static void MoveElements(void* dest, void* src, size_t count, size_t elemSize) { + ElemType* destElem = static_cast(dest); + ElemType* srcElem = static_cast(src); + ElemType* destElemEnd = destElem + count; + ElemType* srcElemEnd = srcElem + count; + if (destElem == srcElem) { + return; // In practice, we don't do this. + } else if (srcElemEnd > destElem && srcElemEnd < destElemEnd) { + while (destElemEnd != destElem) { + --destElemEnd; + --srcElemEnd; + traits::Construct(destElemEnd, *srcElemEnd); + traits::Destruct(srcElem); + } + } else { + CopyElements(dest, src, count, elemSize); + } + } +}; + +// +// The default behaviour is to use memcpy/memmove for everything. +// +template +struct nsTArray_CopyElements : public nsTArray_CopyWithMemutils {}; + +// +// JS::Heap elements require constructors/destructors to be called and so is +// specialized here. +// +template +struct nsTArray_CopyElements > : public nsTArray_CopyWithConstructors {}; + +// +// Base class for nsTArray_Impl that is templated on element type and derived +// nsTArray_Impl class, to allow extra conversions to be added for specific +// types. +// +template +struct nsTArray_TypedBase : public nsTArray_SafeElementAtHelper {}; + +// +// Specialization of nsTArray_TypedBase for arrays containing JS::Heap +// elements. +// +// These conversions are safe because JS::Heap and E share the same +// representation, and since the result of the conversions are const references +// we won't miss any barriers. +// +// The static_cast is necessary to obtain the correct address for the derived +// class since we are a base class used in multiple inheritance. +// +template +struct nsTArray_TypedBase, Derived> + : public nsTArray_SafeElementAtHelper, Derived> +{ + operator const nsTArray& () { + MOZ_STATIC_ASSERT(sizeof(E) == sizeof(JS::Heap), + "JS::Heap must be binary compatible with E."); + Derived* self = static_cast(this); + return *reinterpret_cast *>(self); + } + + operator const FallibleTArray& () { + Derived* self = static_cast(this); + return *reinterpret_cast *>(self); + } +}; + + // // nsTArray_Impl contains most of the guts supporting nsTArray, FallibleTArray, // nsAutoTArray, and AutoFallibleTArray. @@ -576,16 +717,17 @@ struct AssignRangeAlgorithm { // TArrays can be cast to |const nsTArray&|. // template -class nsTArray_Impl : public nsTArray_base, - public nsTArray_SafeElementAtHelper > +class nsTArray_Impl : public nsTArray_base >, + public nsTArray_TypedBase > { public: - typedef nsTArray_base base_type; - typedef typename base_type::size_type size_type; - typedef typename base_type::index_type index_type; - typedef E elem_type; - typedef nsTArray_Impl self_type; - typedef nsTArrayElementTraits elem_traits; + typedef nsTArray_CopyElements copy_type; + typedef nsTArray_base base_type; + typedef typename base_type::size_type size_type; + typedef typename base_type::index_type index_type; + typedef E elem_type; + typedef nsTArray_Impl self_type; + typedef nsTArrayElementTraits elem_traits; typedef nsTArray_SafeElementAtHelper safeelementat_helper_type; using safeelementat_helper_type::SafeElementAt; @@ -716,7 +858,7 @@ public: const elem_type* Elements() const { return reinterpret_cast(Hdr() + 1); } - + // This method provides direct access to the i'th element of the array. // The given index must be within the array bounds. // @param i The index of an element in the array. @@ -1083,7 +1225,7 @@ public: return AppendElements(1); } - // Move all elements from another array to the end of this array without + // Move all elements from another array to the end of this array without // calling copy constructors or destructors. // @return A pointer to the newly appended elements, or null on OOM. template @@ -1093,8 +1235,8 @@ public: index_type otherLen = array.Length(); if (!Alloc::Successful(this->EnsureCapacity(len + otherLen, sizeof(elem_type)))) return nullptr; - memcpy(Elements() + len, array.Elements(), otherLen * sizeof(elem_type)); - this->IncrementLength(otherLen); + copy_type::CopyElements(Elements() + len, array.Elements(), otherLen, sizeof(elem_type)); + this->IncrementLength(otherLen); array.ShiftData(0, otherLen, 0, sizeof(elem_type), MOZ_ALIGNOF(elem_type)); return Elements() + len; } @@ -1201,7 +1343,7 @@ public: if (newLen > oldLen) { return InsertElementsAt(oldLen, newLen - oldLen) != nullptr; } - + TruncateLength(newLen); return true; } @@ -1283,7 +1425,7 @@ typename Alloc::ResultType EnsureLengthAtLeast(size_type minLen) { // // Sorting // - + // This function is meant to be used with the NS_QuickSort function. It // maps the callback API expected by NS_QuickSort to the Comparator API // used by nsTArray_Impl. See nsTArray_Impl::Sort. @@ -1396,8 +1538,8 @@ protected: // This method invokes elem_type's copy-constructor on a range of elements. // @param start The index of the first element to construct. - // @param count The number of elements to construct. - // @param values The array of elements to copy. + // @param count The number of elements to construct. + // @param values The array of elements to copy. template void AssignRange(index_type start, size_type count, const Item *values) { @@ -1533,7 +1675,7 @@ protected: private: // nsTArray_base casts itself as an nsAutoArrayBase in order to get a pointer // to mAutoBuf. - template + template friend class nsTArray_base; void Init() {