Bug 1102525 (part 1) - Add SegmentedVector to MFBT.
This is based on the SegmentedArray type from nsCycleCollector.cpp.
new file mode 100644
--- /dev/null
+++ b/mfbt/SegmentedVector.h
@@ -0,0 +1,186 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+// A simple segmented vector class. It should be used in preference to
+// mozilla::Vector or nsTArray when you are simply gathering items in order to
+// later iterate over them, because it avoids those types' need to continually
+// allocate and copy the data into increasingly large buffers.
+
+#ifndef mozilla_SegmentedVector_h
+#define mozilla_SegmentedVector_h
+
+#include "mozilla/Array.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Move.h"
+#include "mozilla/TypeTraits.h"
+
+#include <new> // for placement new
+
+namespace mozilla {
+
+namespace detail {
+
+template<typename T, size_t SegmentCapacity>
+struct Segment : public mozilla::LinkedListElement<Segment<T, SegmentCapacity>>
+{
+ Segment() : mLength(0) {}
+
+ uint32_t Length() const { return mLength; }
+
+ template<typename U>
+ void Append(U&& aU)
+ {
+ MOZ_ASSERT(mLength < SegmentCapacity);
+ new (&mElems[mLength]) T(mozilla::Forward<U>(aU));
+ mLength++;
+ }
+
+ uint32_t mLength;
+ mozilla::Array<T, SegmentCapacity> mElems;
+};
+
+} // namespace detail
+
+// For a given segment size (in bytes), compute the number of T elements that
+// can fit into it. It's a good idea to use a reasonably large power-of-two for
+// IdealSegmentSize, e.g. 4096 or 8192.
+template<typename T, size_t IdealSegmentSize>
+class SegmentedVectorCapacity
+{
+ static const size_t kSingleElementSegmentSize =
+ sizeof(detail::Segment<T, 1>);
+
+public:
+ // See how many we can fit. If IdealSegmentSize is too small, it'll be just
+ // one. The +1 is because kSingleElementSegmentSize already accounts for one
+ // element.
+ static const size_t value =
+ kSingleElementSegmentSize <= IdealSegmentSize
+ ? (IdealSegmentSize - kSingleElementSegmentSize) / sizeof(T) + 1
+ : 1;
+};
+
+template<class T, size_t SegmentCapacity>
+class SegmentedVector
+{
+ typedef detail::Segment<T, SegmentCapacity> Segment;
+
+public:
+ // The |aIdealSegmentSize| is only for sanity checking. If it's specified, we
+ // check that the actual segment size is as close as possible to it. This
+ // serves as a sanity check for SegmentedVectorCapacity's capacity
+ // computation.
+ SegmentedVector(size_t aIdealSegmentSize = 0)
+ {
+ // The difference between the actual segment size and the ideal segment
+ // size should be less than the size of a single element... unless the
+ // ideal size was too small, in which case the capacity should be one.
+ MOZ_ASSERT_IF(
+ aIdealSegmentSize != 0,
+ (sizeof(Segment) > aIdealSegmentSize && SegmentCapacity == 1) ||
+ aIdealSegmentSize - sizeof(Segment) < sizeof(T));
+ }
+
+ ~SegmentedVector() { Clear(); }
+
+ bool IsEmpty() const { return !mSegments.getFirst(); }
+
+ // Note that this is O(n) rather than O(1), but the constant factor is very
+ // small because it only has to do one addition per segment.
+ size_t Length() const
+ {
+ size_t n = 0;
+ for (auto segment = mSegments.getFirst();
+ segment;
+ segment = segment->getNext()) {
+ n += segment->Length();
+ }
+ return n;
+ }
+
+ template<typename U>
+ void Append(U&& aU)
+ {
+ Segment* last = mSegments.getLast();
+ if (!last || last->Length() == SegmentCapacity) {
+ last = new Segment();
+ mSegments.insertBack(last);
+ }
+ last->Append(mozilla::Forward<U>(aU));
+ }
+
+ void Clear()
+ {
+ Segment* segment;
+ while ((segment = mSegments.popFirst())) {
+ delete segment;
+ }
+ }
+
+ // Use this class to iterate over a SegmentedVector, like so:
+ //
+ // for (auto r = v.All(); !r.IsEmpty(); r.PopFront()) {
+ // MyElem& elem = r.Front();
+ // f(elem);
+ // }
+ //
+ class Range
+ {
+ friend class SegmentedVector;
+
+ Segment* mSegment;
+ size_t mIndex;
+
+ Range(SegmentedVector* aVector)
+ : mSegment(aVector->mSegments.getFirst())
+ , mIndex(0)
+ {}
+
+ public:
+ bool IsEmpty() const { return !mSegment; }
+
+ T& Front() const
+ {
+ MOZ_ASSERT(!IsEmpty());
+ return mSegment->mElems[mIndex];
+ }
+
+ void PopFront()
+ {
+ MOZ_ASSERT(!IsEmpty());
+ mIndex++;
+ if (mIndex == mSegment->Length()) {
+ mSegment = mSegment->getNext();
+ mIndex = 0;
+ }
+ }
+ };
+
+ Range All() { return Range(this); }
+
+ // Measure the memory consumption of the vector excluding |this|. Note that
+ // it only measures the vector itself. If the vector elements contain
+ // pointers to other memory blocks, those blocks must be measured separately
+ // during a subsequent iteration over the vector.
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+ {
+ return mSegments.sizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ // Like sizeOfExcludingThis(), but measures |this| as well.
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+ {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+private:
+ mozilla::LinkedList<Segment> mSegments;
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_SegmentedVector_h */
--- a/mfbt/moz.build
+++ b/mfbt/moz.build
@@ -56,16 +56,17 @@ EXPORTS.mozilla = [
'Poison.h',
'Range.h',
'RangedPtr.h',
'RefCountType.h',
'ReentrancyGuard.h',
'RefPtr.h',
'RollingMean.h',
'Scoped.h',
+ 'SegmentedVector.h',
'SHA1.h',
'SplayTree.h',
'TaggedAnonymousMemory.h',
'TemplateLib.h',
'ThreadLocal.h',
'ToString.h',
'TypedEnum.h',
'TypedEnumBits.h',
new file mode 100644
--- /dev/null
+++ b/mfbt/tests/TestSegmentedVector.cpp
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 9; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+// This is included first to ensure it doesn't implicitly depend on anything
+// else.
+#include "mozilla/SegmentedVector.h"
+
+#include "mozilla/Assertions.h"
+
+using mozilla::SegmentedVector;
+using mozilla::SegmentedVectorCapacity;
+
+// This tests basic segmented vector construction and iteration.
+void TestBasics()
+{
+ // A SegmentedVector with a POD element type.
+ static const size_t cap = SegmentedVectorCapacity<int, 1024>::value;
+ typedef SegmentedVector<int, cap> MyVector;
+ MyVector v(1024);
+ int i, n;
+
+ MOZ_RELEASE_ASSERT(v.IsEmpty());
+
+ // Add 100 elements, then check various things.
+ i = 0;
+ for ( ; i < 100; i++) {
+ v.Append(mozilla::Move(i));
+ }
+ MOZ_RELEASE_ASSERT(!v.IsEmpty());
+ MOZ_RELEASE_ASSERT(v.Length() == 100);
+
+ n = 0;
+ for (MyVector::Range r = v.All(); !r.IsEmpty(); r.PopFront()) {
+ MOZ_RELEASE_ASSERT(r.Front() == n);
+ n++;
+ }
+ MOZ_RELEASE_ASSERT(n == 100);
+
+ // Add another 900 elements, then re-check.
+ for ( ; i < 1000; i++) {
+ v.Append(mozilla::Move(i));
+ }
+ MOZ_RELEASE_ASSERT(!v.IsEmpty());
+ MOZ_RELEASE_ASSERT(v.Length() == 1000);
+
+ n = 0;
+ for (MyVector::Range r = v.All(); !r.IsEmpty(); r.PopFront()) {
+ MOZ_RELEASE_ASSERT(r.Front() == n);
+ n++;
+ }
+ MOZ_RELEASE_ASSERT(n == 1000);
+
+ v.Clear();
+ MOZ_RELEASE_ASSERT(v.IsEmpty());
+ MOZ_RELEASE_ASSERT(v.Length() == 0);
+}
+
+// This tests how segmented vectors with non-POD elements construct and
+// destruct those elements.
+void TestConstructorsAndDestructors()
+{
+ static int gNumDefaultCtors;
+ static int gNumExplicitCtors;
+ static int gNumCopyCtors;
+ static int gNumMoveCtors;
+ static int gNumDtors;
+
+ struct NonPOD
+ {
+ NonPOD() { gNumDefaultCtors++; }
+ explicit NonPOD(int x) { gNumExplicitCtors++; }
+ NonPOD(NonPOD&) { gNumCopyCtors++; }
+ NonPOD(NonPOD&&) { gNumMoveCtors++; }
+ ~NonPOD() { gNumDtors++; }
+ };
+
+ {
+ // A SegmentedVector with a non-POD element type.
+ NonPOD x(1); // explicit constructor called
+ SegmentedVector<NonPOD, 10> v; // default constructor called 10x
+ MOZ_RELEASE_ASSERT(v.IsEmpty());
+ v.Append(x); // copy constructor called
+ NonPOD y(1); // explicit constructor called
+ v.Append(mozilla::Move(y)); // move constructor called
+ NonPOD z(1); // explicit constructor called
+ v.Append(mozilla::Move(z)); // move constructor called
+ v.Clear(); // destructor called 10x
+
+ MOZ_RELEASE_ASSERT(gNumDefaultCtors == 10);
+ MOZ_RELEASE_ASSERT(gNumExplicitCtors == 3);
+ MOZ_RELEASE_ASSERT(gNumCopyCtors == 1);
+ MOZ_RELEASE_ASSERT(gNumMoveCtors == 2);
+ MOZ_RELEASE_ASSERT(gNumDtors == 10);
+ } // destructor called for x, y, z
+ MOZ_RELEASE_ASSERT(gNumDtors == 13);
+}
+
+// This tests that SegmentedVectorCapacity gives the right segment capacities
+// for specified segment sizes.
+void TestSegmentSizing()
+{
+ struct A { int mX; int mY; };
+ struct B { int mX; char mY; double mZ; };
+ struct C { A mA; B mB; };
+ struct D { char mBuf[101]; };
+ struct E { };
+
+ // When SegmentedVector's constructor is passed a size, it asserts that the
+ // vector's segment capacity results in a segment size equal to (or very
+ // close to) the passed size.
+ SegmentedVector<double, SegmentedVectorCapacity<double, 512>::value> v1(512);
+ SegmentedVector<A, SegmentedVectorCapacity<A, 1024>::value> v2(1024);
+ SegmentedVector<B, SegmentedVectorCapacity<B, 999>::value> v3(999);
+ SegmentedVector<C, SegmentedVectorCapacity<C, 10>::value> v4(10);
+ SegmentedVector<D, SegmentedVectorCapacity<D, 1234>::value> v5(1234);
+ SegmentedVector<E, SegmentedVectorCapacity<E, 4096>::value> v6(4096);
+}
+
+int main(void)
+{
+ TestBasics();
+ TestConstructorsAndDestructors();
+ TestSegmentSizing();
+
+ return 0;
+}
--- a/mfbt/tests/moz.build
+++ b/mfbt/tests/moz.build
@@ -20,16 +20,17 @@ CppUnitTests([
'TestIntegerPrintfMacros',
'TestJSONWriter',
'TestMacroArgs',
'TestMacroForEach',
'TestMaybe',
'TestPair',
'TestRefPtr',
'TestRollingMean',
+ 'TestSegmentedVector',
'TestSHA1',
'TestSplayTree',
'TestTypedEnum',
'TestTypeTraits',
'TestUniquePtr',
'TestWeakPtr',
])