Merge remote-tracking branch 'origin/tracking' into custom

This commit is contained in:
roytam1 2023-03-15 22:52:23 +08:00
commit 85f6a49297
19 changed files with 347 additions and 170 deletions

View file

@ -146,7 +146,7 @@ struct VectorImpl
aV.free_(aV.mBegin);
aV.mBegin = newbuf;
/* aV.mLength is unchanged. */
aV.mCapacity = aNewCap;
aV.mTail.mCapacity = aNewCap;
return true;
}
};
@ -225,28 +225,30 @@ struct VectorImpl<T, N, AP, true>
{
MOZ_ASSERT(!aV.usingInlineStorage());
MOZ_ASSERT(!CapacityHasExcessSpace<T>(aNewCap));
T* newbuf = aV.template pod_realloc<T>(aV.mBegin, aV.mCapacity, aNewCap);
T* newbuf =
aV.template pod_realloc<T>(aV.mBegin, aV.mTail.mCapacity, aNewCap);
if (MOZ_UNLIKELY(!newbuf)) {
return false;
}
aV.mBegin = newbuf;
/* aV.mLength is unchanged. */
aV.mCapacity = aNewCap;
aV.mTail.mCapacity = aNewCap;
return true;
}
static inline void
podResizeToFit(Vector<T, N, AP>& aV)
{
if (aV.usingInlineStorage() || aV.mLength == aV.mCapacity) {
if (aV.usingInlineStorage() || aV.mLength == aV.mTail.mCapacity) {
return;
}
T* newbuf = aV.template pod_realloc<T>(aV.mBegin, aV.mCapacity, aV.mLength);
T* newbuf =
aV.template pod_realloc<T>(aV.mBegin, aV.mTail.mCapacity, aV.mLength);
if (MOZ_UNLIKELY(!newbuf)) {
return;
}
aV.mBegin = newbuf;
aV.mCapacity = aV.mLength;
aV.mTail.mCapacity = aV.mLength;
}
};
@ -277,7 +279,7 @@ struct VectorTesting;
template<typename T,
size_t MinInlineCapacity = 0,
class AllocPolicy = MallocAllocPolicy>
class Vector final : private AllocPolicy
class MOZ_NON_PARAM Vector final : private AllocPolicy
{
/* utilities */
@ -293,36 +295,39 @@ class Vector final : private AllocPolicy
/* magic constants */
static const int kMaxInlineBytes = 1024;
/* compute constants */
/*
* Consider element size to be 1 for buffer sizing if there are 0 inline
* elements. This allows us to compile when the definition of the element
* type is not visible here.
/**
* The maximum space allocated for inline element storage.
*
* Explicit specialization is only allowed at namespace scope, so in order
* to keep everything here, we use a dummy template parameter with partial
* specialization.
* We reduce space by what the AllocPolicy base class and prior Vector member
* fields likely consume to attempt to play well with binary size classes.
*/
template<int M, int Dummy>
struct ElemSize
{
static const size_t value = sizeof(T);
};
template<int Dummy>
struct ElemSize<0, Dummy>
{
static const size_t value = 1;
static constexpr size_t kMaxInlineBytes =
1024 -
(sizeof(AllocPolicy) + sizeof(T*) + sizeof(size_t) + sizeof(size_t));
/**
* The number of T elements of inline capacity built into this Vector. This
* is usually |MinInlineCapacity|, but it may be less (or zero!) for large T.
*
* We use a partially-specialized template (not explicit specialization, which
* is only allowed at namespace scope) to compute this value. The benefit is
* that |sizeof(T)| need not be computed, and |T| doesn't have to be fully
* defined at the time |Vector<T>| appears, if no inline storage is requested.
*/
template <size_t MinimumInlineCapacity, size_t Dummy>
struct ComputeCapacity {
static constexpr size_t value =
tl::Min<MinimumInlineCapacity, kMaxInlineBytes / sizeof(T)>::value;
};
static const size_t kInlineCapacity =
tl::Min<MinInlineCapacity, kMaxInlineBytes / ElemSize<MinInlineCapacity, 0>::value>::value;
template <size_t Dummy>
struct ComputeCapacity<0, Dummy> {
static constexpr size_t value = 0;
};
/* Calculate inline buffer size; avoid 0-sized array. */
static const size_t kInlineBytes =
tl::Max<1, kInlineCapacity * ElemSize<MinInlineCapacity, 0>::value>::value;
/** The actual inline capacity in number of elements T. This may be zero! */
static constexpr size_t kInlineCapacity =
ComputeCapacity<MinInlineCapacity, 0>::value;
/* member data */
@ -338,16 +343,84 @@ class Vector final : private AllocPolicy
/* Number of elements in the vector. */
size_t mLength;
/* Max number of elements storable in the vector without resizing. */
size_t mCapacity;
/*
* Memory used to store capacity, reserved element count (debug builds only),
* and inline storage. The simple "answer" is:
*
* size_t mCapacity;
* #ifdef DEBUG
* size_t mReserved;
* #endif
* alignas(T) unsigned char mBytes[kInlineCapacity * sizeof(T)];
*
* but there are complications. First, C++ forbids zero-sized arrays that
* might result. Second, we don't want zero capacity to affect Vector's size
* (even empty classes take up a byte, unless they're base classes).
*
* Yet again, we eliminate the zero-sized array using partial specialization.
* And we eliminate potential size hit by putting capacity/reserved in one
* struct, then putting the array (if any) in a derived struct. If no array
* is needed, the derived struct won't consume extra space.
*/
struct CapacityAndReserved
{
explicit CapacityAndReserved(size_t aCapacity, size_t aReserved)
: mCapacity(aCapacity)
#ifdef DEBUG
, mReserved(aReserved)
#endif
{}
CapacityAndReserved() = default;
/* Max number of elements storable in the vector without resizing. */
size_t mCapacity;
#ifdef DEBUG
/* Max elements of reserved or used space in this vector. */
size_t mReserved;
/* Max elements of reserved or used space in this vector. */
size_t mReserved;
#endif
};
/* Memory used for inline storage. */
AlignedStorage<kInlineBytes> mStorage;
// Silence warnings about this struct possibly being padded dued to the
// alignas() in it -- there's nothing we can do to avoid it.
#ifdef _MSC_VER
# pragma warning(push)
# pragma warning(disable:4324)
#endif // _MSC_VER
template<size_t Capacity, size_t Dummy>
struct CRAndStorage : CapacityAndReserved
{
explicit CRAndStorage(size_t aCapacity, size_t aReserved)
: CapacityAndReserved(aCapacity, aReserved)
{}
CRAndStorage() = default;
alignas(T) unsigned char mBytes[Capacity * sizeof(T)];
// GCC fails due to -Werror=strict-aliasing if |mBytes| is directly cast to
// T*. Indirecting through this function addresses the problem.
void* data() { return mBytes; }
T* storage() { return static_cast<T*>(data()); }
};
template<size_t Dummy>
struct CRAndStorage<0, Dummy> : CapacityAndReserved
{
explicit CRAndStorage(size_t aCapacity, size_t aReserved)
: CapacityAndReserved(aCapacity, aReserved)
{}
CRAndStorage() = default;
T* storage() { return nullptr; }
};
CRAndStorage<kInlineCapacity, 0> mTail;
#ifdef _MSC_VER
# pragma warning(pop)
#endif // _MSC_VER
#ifdef DEBUG
friend class ReentrancyGuard;
@ -363,7 +436,7 @@ class Vector final : private AllocPolicy
T* inlineStorage()
{
return static_cast<T*>(mStorage.addr());
return mTail.storage();
}
T* beginNoCheck() const
@ -391,9 +464,9 @@ class Vector final : private AllocPolicy
*/
size_t reserved() const
{
MOZ_ASSERT(mLength <= mReserved);
MOZ_ASSERT(mReserved <= mCapacity);
return mReserved;
MOZ_ASSERT(mLength <= mTail.mReserved);
MOZ_ASSERT(mTail.mReserved <= mTail.mCapacity);
return mTail.mReserved;
}
#endif
@ -426,7 +499,7 @@ public:
bool empty() const { return mLength == 0; }
size_t capacity() const { return mCapacity; }
size_t capacity() const { return mTail.mCapacity; }
T* begin()
{
@ -753,10 +826,10 @@ private:
/* This does the re-entrancy check plus several other sanity checks. */
#define MOZ_REENTRANCY_GUARD_ET_AL \
ReentrancyGuard g(*this); \
MOZ_ASSERT_IF(usingInlineStorage(), mCapacity == kInlineCapacity); \
MOZ_ASSERT(reserved() <= mCapacity); \
MOZ_ASSERT_IF(usingInlineStorage(), mTail.mCapacity == kInlineCapacity); \
MOZ_ASSERT(reserved() <= mTail.mCapacity); \
MOZ_ASSERT(mLength <= reserved()); \
MOZ_ASSERT(mLength <= mCapacity)
MOZ_ASSERT(mLength <= mTail.mCapacity)
/* Vector Implementation */
@ -765,13 +838,12 @@ MOZ_ALWAYS_INLINE
Vector<T, N, AP>::Vector(AP aAP)
: AP(aAP)
, mLength(0)
, mCapacity(kInlineCapacity)
, mTail(kInlineCapacity, 0)
#ifdef DEBUG
, mReserved(0)
, mEntered(false)
#endif
{
mBegin = static_cast<T*>(mStorage.addr());
mBegin = inlineStorage();
}
/* Move constructor. */
@ -784,14 +856,14 @@ Vector<T, N, AllocPolicy>::Vector(Vector&& aRhs)
#endif
{
mLength = aRhs.mLength;
mCapacity = aRhs.mCapacity;
mTail.mCapacity = aRhs.mTail.mCapacity;
#ifdef DEBUG
mReserved = aRhs.mReserved;
mTail.mReserved = aRhs.mTail.mReserved;
#endif
if (aRhs.usingInlineStorage()) {
/* We can't move the buffer over in this case, so copy elements. */
mBegin = static_cast<T*>(mStorage.addr());
mBegin = inlineStorage();
Impl::moveConstruct(mBegin, aRhs.beginNoCheck(), aRhs.endNoCheck());
/*
* Leave aRhs's mLength, mBegin, mCapacity, and mReserved as they are.
@ -803,11 +875,11 @@ Vector<T, N, AllocPolicy>::Vector(Vector&& aRhs)
* in-line storage.
*/
mBegin = aRhs.mBegin;
aRhs.mBegin = static_cast<T*>(aRhs.mStorage.addr());
aRhs.mCapacity = kInlineCapacity;
aRhs.mBegin = aRhs.inlineStorage();
aRhs.mTail.mCapacity = kInlineCapacity;
aRhs.mLength = 0;
#ifdef DEBUG
aRhs.mReserved = 0;
aRhs.mTail.mReserved = 0;
#endif
}
}
@ -871,7 +943,7 @@ Vector<T, N, AP>::convertToHeapStorage(size_t aNewCap)
/* Switch in heap buffer. */
mBegin = newBuf;
/* mLength is unchanged. */
mCapacity = aNewCap;
mTail.mCapacity = aNewCap;
return true;
}
@ -879,7 +951,7 @@ template<typename T, size_t N, class AP>
MOZ_NEVER_INLINE bool
Vector<T, N, AP>::growStorageBy(size_t aIncr)
{
MOZ_ASSERT(mLength + aIncr > mCapacity);
MOZ_ASSERT(mLength + aIncr > mTail.mCapacity);
/*
* When choosing a new capacity, its size should is as close to 2**N bytes
@ -971,9 +1043,9 @@ Vector<T, N, AP>::initCapacity(size_t aRequest)
return false;
}
mBegin = newbuf;
mCapacity = aRequest;
mTail.mCapacity = aRequest;
#ifdef DEBUG
mReserved = aRequest;
mTail.mReserved = aRequest;
#endif
return true;
}
@ -998,7 +1070,7 @@ Vector<T, N, AP>::maybeCheckSimulatedOOM(size_t aRequestedSize)
}
#ifdef DEBUG
if (aRequestedSize <= mReserved) {
if (aRequestedSize <= mTail.mReserved) {
return true;
}
#endif
@ -1011,7 +1083,7 @@ inline bool
Vector<T, N, AP>::reserve(size_t aRequest)
{
MOZ_REENTRANCY_GUARD_ET_AL;
if (aRequest > mCapacity) {
if (aRequest > mTail.mCapacity) {
if (MOZ_UNLIKELY(!growStorageBy(aRequest - mLength))) {
return false;
}
@ -1019,11 +1091,11 @@ Vector<T, N, AP>::reserve(size_t aRequest)
return false;
}
#ifdef DEBUG
if (aRequest > mReserved) {
mReserved = aRequest;
if (aRequest > mTail.mReserved) {
mTail.mReserved = aRequest;
}
MOZ_ASSERT(mLength <= mReserved);
MOZ_ASSERT(mReserved <= mCapacity);
MOZ_ASSERT(mLength <= mTail.mReserved);
MOZ_ASSERT(mTail.mReserved <= mTail.mCapacity);
#endif
return true;
}
@ -1051,20 +1123,20 @@ MOZ_ALWAYS_INLINE bool
Vector<T, N, AP>::growBy(size_t aIncr)
{
MOZ_REENTRANCY_GUARD_ET_AL;
if (aIncr > mCapacity - mLength) {
if (aIncr > mTail.mCapacity - mLength) {
if (MOZ_UNLIKELY(!growStorageBy(aIncr))) {
return false;
}
} else if (!maybeCheckSimulatedOOM(mLength + aIncr)) {
return false;
}
MOZ_ASSERT(mLength + aIncr <= mCapacity);
MOZ_ASSERT(mLength + aIncr <= mTail.mCapacity);
T* newend = endNoCheck() + aIncr;
Impl::initialize(endNoCheck(), newend);
mLength += aIncr;
#ifdef DEBUG
if (mLength > mReserved) {
mReserved = mLength;
if (mLength > mTail.mReserved) {
mTail.mReserved = mLength;
}
#endif
return true;
@ -1075,7 +1147,7 @@ MOZ_ALWAYS_INLINE bool
Vector<T, N, AP>::growByUninitialized(size_t aIncr)
{
MOZ_REENTRANCY_GUARD_ET_AL;
if (aIncr > mCapacity - mLength) {
if (aIncr > mTail.mCapacity - mLength) {
if (MOZ_UNLIKELY(!growStorageBy(aIncr))) {
return false;
}
@ -1083,8 +1155,8 @@ Vector<T, N, AP>::growByUninitialized(size_t aIncr)
return false;
}
#ifdef DEBUG
if (mLength + aIncr > mReserved) {
mReserved = mLength + aIncr;
if (mLength + aIncr > mTail.mReserved) {
mTail.mReserved = mLength + aIncr;
}
#endif
infallibleGrowByUninitialized(aIncr);
@ -1142,10 +1214,10 @@ Vector<T, N, AP>::clearAndFree()
return;
}
this->free_(beginNoCheck());
mBegin = static_cast<T*>(mStorage.addr());
mCapacity = kInlineCapacity;
mBegin = inlineStorage();
mTail.mCapacity = kInlineCapacity;
#ifdef DEBUG
mReserved = 0;
mTail.mReserved = 0;
#endif
}
@ -1162,7 +1234,7 @@ template<typename T, size_t N, class AP>
inline bool
Vector<T, N, AP>::canAppendWithoutRealloc(size_t aNeeded) const
{
return mLength + aNeeded <= mCapacity;
return mLength + aNeeded <= mTail.mCapacity;
}
template<typename T, size_t N, class AP>
@ -1178,8 +1250,8 @@ template<typename U>
MOZ_ALWAYS_INLINE void
Vector<T, N, AP>::internalAppend(U&& aU)
{
MOZ_ASSERT(mLength + 1 <= mReserved);
MOZ_ASSERT(mReserved <= mCapacity);
MOZ_ASSERT(mLength + 1 <= mTail.mReserved);
MOZ_ASSERT(mTail.mReserved <= mTail.mCapacity);
Impl::new_(endNoCheck(), Forward<U>(aU));
++mLength;
}
@ -1189,7 +1261,7 @@ MOZ_ALWAYS_INLINE bool
Vector<T, N, AP>::appendN(const T& aT, size_t aNeeded)
{
MOZ_REENTRANCY_GUARD_ET_AL;
if (mLength + aNeeded > mCapacity) {
if (mLength + aNeeded > mTail.mCapacity) {
if (MOZ_UNLIKELY(!growStorageBy(aNeeded))) {
return false;
}
@ -1197,8 +1269,8 @@ Vector<T, N, AP>::appendN(const T& aT, size_t aNeeded)
return false;
}
#ifdef DEBUG
if (mLength + aNeeded > mReserved) {
mReserved = mLength + aNeeded;
if (mLength + aNeeded > mTail.mReserved) {
mTail.mReserved = mLength + aNeeded;
}
#endif
internalAppendN(aT, aNeeded);
@ -1209,8 +1281,8 @@ template<typename T, size_t N, class AP>
MOZ_ALWAYS_INLINE void
Vector<T, N, AP>::internalAppendN(const T& aT, size_t aNeeded)
{
MOZ_ASSERT(mLength + aNeeded <= mReserved);
MOZ_ASSERT(mReserved <= mCapacity);
MOZ_ASSERT(mLength + aNeeded <= mTail.mReserved);
MOZ_ASSERT(mTail.mReserved <= mTail.mCapacity);
Impl::copyConstructN(endNoCheck(), aNeeded, aT);
mLength += aNeeded;
}
@ -1275,7 +1347,7 @@ Vector<T, N, AP>::append(const U* aInsBegin, const U* aInsEnd)
{
MOZ_REENTRANCY_GUARD_ET_AL;
size_t aNeeded = PointerRangeSize(aInsBegin, aInsEnd);
if (mLength + aNeeded > mCapacity) {
if (mLength + aNeeded > mTail.mCapacity) {
if (MOZ_UNLIKELY(!growStorageBy(aNeeded))) {
return false;
}
@ -1283,8 +1355,8 @@ Vector<T, N, AP>::append(const U* aInsBegin, const U* aInsEnd)
return false;
}
#ifdef DEBUG
if (mLength + aNeeded > mReserved) {
mReserved = mLength + aNeeded;
if (mLength + aNeeded > mTail.mReserved) {
mTail.mReserved = mLength + aNeeded;
}
#endif
internalAppend(aInsBegin, aNeeded);
@ -1296,8 +1368,8 @@ template<typename U>
MOZ_ALWAYS_INLINE void
Vector<T, N, AP>::internalAppend(const U* aInsBegin, size_t aInsLength)
{
MOZ_ASSERT(mLength + aInsLength <= mReserved);
MOZ_ASSERT(mReserved <= mCapacity);
MOZ_ASSERT(mLength + aInsLength <= mTail.mReserved);
MOZ_ASSERT(mTail.mReserved <= mTail.mCapacity);
Impl::copyConstruct(endNoCheck(), aInsBegin, aInsBegin + aInsLength);
mLength += aInsLength;
}
@ -1308,7 +1380,7 @@ MOZ_ALWAYS_INLINE bool
Vector<T, N, AP>::append(U&& aU)
{
MOZ_REENTRANCY_GUARD_ET_AL;
if (mLength == mCapacity) {
if (mLength == mTail.mCapacity) {
if (MOZ_UNLIKELY(!growStorageBy(1))) {
return false;
}
@ -1316,8 +1388,8 @@ Vector<T, N, AP>::append(U&& aU)
return false;
}
#ifdef DEBUG
if (mLength + 1 > mReserved) {
mReserved = mLength + 1;
if (mLength + 1 > mTail.mReserved) {
mTail.mReserved = mLength + 1;
}
#endif
internalAppend(Forward<U>(aU));
@ -1370,11 +1442,11 @@ Vector<T, N, AP>::extractRawBuffer()
}
T* ret = mBegin;
mBegin = static_cast<T*>(mStorage.addr());
mBegin = inlineStorage();
mLength = 0;
mCapacity = kInlineCapacity;
mTail.mCapacity = kInlineCapacity;
#ifdef DEBUG
mReserved = 0;
mTail.mReserved = 0;
#endif
return ret;
}
@ -1396,11 +1468,11 @@ Vector<T, N, AP>::extractOrCopyRawBuffer()
Impl::moveConstruct(copy, beginNoCheck(), endNoCheck());
Impl::destroy(beginNoCheck(), endNoCheck());
mBegin = static_cast<T*>(mStorage.addr());
mBegin = inlineStorage();
mLength = 0;
mCapacity = kInlineCapacity;
mTail.mCapacity = kInlineCapacity;
#ifdef DEBUG
mReserved = 0;
mTail.mReserved = 0;
#endif
return copy;
}
@ -1424,19 +1496,19 @@ Vector<T, N, AP>::replaceRawBuffer(T* aP, size_t aLength)
* otherwise be acceptable. Maybe this behaviour should be
* specifiable with an argument to this function.
*/
mBegin = static_cast<T*>(mStorage.addr());
mBegin = inlineStorage();
mLength = aLength;
mCapacity = kInlineCapacity;
mTail.mCapacity = kInlineCapacity;
Impl::moveConstruct(mBegin, aP, aP + aLength);
Impl::destroy(aP, aP + aLength);
this->free_(aP);
} else {
mBegin = aP;
mLength = aLength;
mCapacity = aLength;
mTail.mCapacity = aLength;
}
#ifdef DEBUG
mReserved = aLength;
mTail.mReserved = aLength;
#endif
}
@ -1475,9 +1547,9 @@ Vector<T, N, AP>::swap(Vector& aOther)
}
Swap(mLength, aOther.mLength);
Swap(mCapacity, aOther.mCapacity);
Swap(mTail.mCapacity, aOther.mTail.mCapacity);
#ifdef DEBUG
Swap(mReserved, aOther.mReserved);
Swap(mTail.mReserved, aOther.mTail.mReserved);
#endif
}

View file

@ -396,6 +396,49 @@ mozilla::detail::VectorTesting::testInsert()
MOZ_RELEASE_ASSERT(S::destructCount == 1);
}
// Declare but leave (permanently) incomplete.
struct Incomplete;
// We could even *construct* a Vector<Incomplete, 0> if we wanted. But we can't
// destruct it, so it's not worth the trouble.
static_assert(sizeof(Vector<Incomplete, 0>) > 0,
"Vector of an incomplete type will compile");
// Vector with no inline storage should occupy the absolute minimum space in
// non-debug builds. (Debug adds a laundry list of other constraints, none
// directly relevant to shipping builds, that aren't worth precisely modeling.)
#ifndef DEBUG
template<typename T>
struct NoInlineStorageLayout
{
T* mBegin;
size_t mLength;
struct CRAndStorage {
size_t mCapacity;
} mTail;
};
// Only one of these should be necessary, but test a few of them for good
// measure.
static_assert(sizeof(Vector<int, 0>) == sizeof(NoInlineStorageLayout<int>),
"Vector of int without inline storage shouldn't occupy dead "
"space for that absence of storage");
static_assert(sizeof(Vector<bool, 0>) == sizeof(NoInlineStorageLayout<bool>),
"Vector of bool without inline storage shouldn't occupy dead "
"space for that absence of storage");
static_assert(sizeof(Vector<S, 0>) == sizeof(NoInlineStorageLayout<S>),
"Vector of S without inline storage shouldn't occupy dead "
"space for that absence of storage");
static_assert(sizeof(Vector<Incomplete, 0>) == sizeof(NoInlineStorageLayout<Incomplete>),
"Vector of an incomplete class without inline storage shouldn't "
"occupy dead space for that absence of storage");
#endif // DEBUG
int
main()
{