Bug 1466928 - Make mozilla::SmallPointerArray compatible with the C++ object model. r=froydnj

--HG--
extra : rebase_source : e803add704e25f981bd8609405fc6f2967f40b05
This commit is contained in:
Jeff Walden 2018-06-07 03:53:54 -07:00
parent d3152e159d
commit 784f62bdc2

View file

@ -5,18 +5,21 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* A vector of pointers space-optimized for a small number of elements. */ /* A vector of pointers space-optimized for a small number of elements. */
#ifndef mozilla_SmallPointerArray_h #ifndef mozilla_SmallPointerArray_h
#define mozilla_SmallPointerArray_h #define mozilla_SmallPointerArray_h
#include "mozilla/Assertions.h" #include "mozilla/Assertions.h"
#include <algorithm> #include <algorithm>
#include <iterator> #include <iterator>
#include <new>
#include <vector> #include <vector>
namespace mozilla { namespace mozilla {
// Array class for situations where a small number of elements (<= 2) is // Array class for situations where a small number of NON-NULL elements (<= 2)
// expected, a large number of elements must be accomodated if necessary, // is expected, a large number of elements must be accomodated if necessary,
// and the size of the class must be minimal. Typical vector implementations // and the size of the class must be minimal. Typical vector implementations
// will fulfill the first two requirements by simply adding inline storage // will fulfill the first two requirements by simply adding inline storage
// alongside the rest of their member variables. While this strategy works, // alongside the rest of their member variables. While this strategy works,
@ -36,30 +39,28 @@ class SmallPointerArray
public: public:
SmallPointerArray() SmallPointerArray()
{ {
mInlineElements[0] = mInlineElements[1] = nullptr; // List-initialization would be nicer, but it only lets you initialize the
static_assert(sizeof(SmallPointerArray<T>) == (2 * sizeof(void*)), // first union member.
"SmallPointerArray must compile to the size of 2 pointers"); mArray[0].mValue = nullptr;
static_assert(offsetof(SmallPointerArray<T>, mArray) == mArray[1].mVector = nullptr;
offsetof(SmallPointerArray<T>, mInlineElements) + sizeof(T*),
"mArray and mInlineElements[1] are expected to overlap in memory");
static_assert(offsetof(SmallPointerArray<T>, mPadding) ==
offsetof(SmallPointerArray<T>, mInlineElements),
"mPadding and mInlineElements[0] are expected to overlap in memory");
} }
~SmallPointerArray() ~SmallPointerArray()
{ {
if (!mInlineElements[0] && mArray) { if (!first()) {
delete mArray; delete maybeVector();
} }
} }
void Clear() { void Clear() {
if (!mInlineElements[0] && mArray) { if (first()) {
delete mArray; first() = nullptr;
mArray = nullptr; new (&mArray[1].mValue) std::vector<T*>*(nullptr);
return; return;
} }
mInlineElements[0] = mInlineElements[1] = nullptr;
delete maybeVector();
mArray[1].mVector = nullptr;
} }
void AppendElement(T* aElement) { void AppendElement(T* aElement) {
@ -69,32 +70,30 @@ public:
// In addition to this we assert in debug builds to point out mistakes to // In addition to this we assert in debug builds to point out mistakes to
// users of the class. // users of the class.
MOZ_ASSERT(aElement != nullptr); MOZ_ASSERT(aElement != nullptr);
if (!mInlineElements[0]) { if (aElement == nullptr) {
if (!mArray) { return;
mInlineElements[0] = aElement; }
// Harmless if aElement == nullptr;
if (!first()) {
auto* vec = maybeVector();
if (!vec) {
first() = aElement;
new (&mArray[1].mValue) T*(nullptr);
return; return;
} }
if (!aElement) { vec->push_back(aElement);
return;
}
mArray->push_back(aElement);
return; return;
} }
if (!aElement) { if (!second()) {
second() = aElement;
return; return;
} }
if (!mInlineElements[1]) { auto* vec = new std::vector<T*>({ first(), second(), aElement });
mInlineElements[1] = aElement; first() = nullptr;
return; new (&mArray[1].mVector) std::vector<T*>*(vec);
}
mArray = new std::vector<T*>({ mInlineElements[0], mInlineElements[1], aElement });
mInlineElements[0] = nullptr;
} }
bool RemoveElement(T* aElement) { bool RemoveElement(T* aElement) {
@ -103,25 +102,31 @@ public:
return false; return false;
} }
if (mInlineElements[0] == aElement) { if (first() == aElement) {
// Expectected case. // Expected case.
mInlineElements[0] = mInlineElements[1]; T* maybeSecond = second();
mInlineElements[1] = nullptr; first() = maybeSecond;
if (maybeSecond) {
second() = nullptr;
} else {
new (&mArray[1].mVector) std::vector<T*>*(nullptr);
}
return true; return true;
} }
if (mInlineElements[0]) { if (first()) {
if (mInlineElements[1] == aElement) { if (second() == aElement) {
mInlineElements[1] = nullptr; second() = nullptr;
return true; return true;
} }
return false; return false;
} }
if (mArray) { if (auto* vec = maybeVector()) {
for (auto iter = mArray->begin(); iter != mArray->end(); iter++) { for (auto iter = vec->begin(); iter != vec->end(); iter++) {
if (*iter == aElement) { if (*iter == aElement) {
mArray->erase(iter); vec->erase(iter);
return true; return true;
} }
} }
@ -135,35 +140,25 @@ public:
return false; return false;
} }
if (mInlineElements[0] == aElement) { if (T* v = first()) {
return true; return v == aElement || second() == aElement;
} }
if (mInlineElements[0]) { if (auto* vec = maybeVector()) {
if (mInlineElements[1] == aElement) { return std::find(vec->begin(), vec->end(), aElement) != vec->end();
return true;
}
return false;
} }
if (mArray) {
return std::find(mArray->begin(), mArray->end(), aElement) != mArray->end();
}
return false; return false;
} }
size_t Length() const size_t Length() const
{ {
if (mInlineElements[0]) { if (first()) {
if (!mInlineElements[1]) { return second() ? 2 : 1;
return 1;
}
return 2;
} }
if (mArray) { if (auto* vec = maybeVector()) {
return mArray->size(); return vec->size();
} }
return 0; return 0;
@ -171,11 +166,13 @@ public:
T* ElementAt(size_t aIndex) const { T* ElementAt(size_t aIndex) const {
MOZ_ASSERT(aIndex < Length()); MOZ_ASSERT(aIndex < Length());
if (mInlineElements[0]) { if (first()) {
return mInlineElements[aIndex]; return mArray[aIndex].mValue;
} }
return (*mArray)[aIndex]; auto* vec = maybeVector();
MOZ_ASSERT(vec, "must have backing vector if accessing an element");
return (*vec)[aIndex];
} }
T* operator[](size_t aIndex) const T* operator[](size_t aIndex) const
@ -183,8 +180,8 @@ public:
return ElementAt(aIndex); return ElementAt(aIndex);
} }
typedef T** iterator; using iterator = T**;
typedef const T** const_iterator; using const_iterator = const T**;
// Methods for range-based for loops. Manipulation invalidates these. // Methods for range-based for loops. Manipulation invalidates these.
iterator begin() { iterator begin() {
@ -204,37 +201,71 @@ public:
private: private:
T** beginInternal() const { T** beginInternal() const {
if (mInlineElements[0] || !mArray) { if (first()) {
return const_cast<T**>(&mInlineElements[0]); static_assert(sizeof(T*) == sizeof(Element),
"pointer ops on &first() must produce adjacent "
"Element::mValue arms");
return &first();
} }
if (mArray->empty()) { auto* vec = maybeVector();
if (!vec) {
return &first();
}
if (vec->empty()) {
return nullptr; return nullptr;
} }
return &(*mArray)[0]; return &(*vec)[0];
} }
// mArray and mInlineElements[1] share the same area in memory. // Accessors for |mArray| element union arms.
T*& first() const {
return const_cast<T*&>(mArray[0].mValue);
}
T*& second() const {
MOZ_ASSERT(first(), "first() must be non-null to have a T* second pointer");
return const_cast<T*&>(mArray[1].mValue);
}
std::vector<T*>* maybeVector() const {
MOZ_ASSERT(!first(),
"function must only be called when this is either empty or has "
"std::vector-backed elements");
return mArray[1].mVector;
}
// In C++ active-union-arm terms:
// //
// When !mInlineElements[0] && !mInlineElements[1] the array is empty. // - mArray[0].mValue is always active: a possibly null T*;
// - if mArray[0].mValue is null, mArray[1].mVector is active: a possibly
// null std::vector<T*>*; if mArray[0].mValue isn't null, mArray[1].mValue
// is active: a possibly null T*.
// //
// When mInlineElements[0] && !mInlineElements[1], mInlineElements[0] // SmallPointerArray begins empty, with mArray[1].mVector active and null.
// contains the first element. The array is of size 1. // Code that makes mArray[0].mValue non-null, i.e. assignments to first(),
// must placement-new mArray[1].mValue with the proper value; code that goes
// the opposite direction, making mArray[0].mValue null, must placement-new
// mArray[1].mVector with the proper value.
// //
// When mInlineElements[0] && mInlineElements[1], mInlineElements[0] // When !mArray[0].mValue && !mArray[1].mVector, the array is empty.
// contains the first element and mInlineElements[1] the second. The
// array is of size 2.
// //
// When !mInlineElements[0] && mArray, mArray contains the full contents // When mArray[0].mValue && !mArray[1].mValue, the array has size 1 and
// of the array and is of arbitrary size. // contains mArray[0].mValue.
union { //
T* mInlineElements[2]; // When mArray[0] && mArray[1], the array has size 2 and contains
struct { // mArray[0].mValue and mArray[1].mValue.
void* mPadding; //
std::vector<T*>* mArray; // When !mArray[0].mValue && mArray[1].mVector, mArray[1].mVector contains
}; // the contents of an array of arbitrary size (even less than two if it ever
}; // contained three elements and elements were removed).
union Element {
T* mValue;
std::vector<T*>* mVector;
} mArray[2];
}; };
} // namespace mozilla } // namespace mozilla