/* -*- 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/Alignment.h"
#include "mozilla/Assertions.h"
using mozilla::SegmentedVector;
// It would be nice if we could use the InfallibleAllocPolicy from mozalloc,
// but MFBT cannot use mozalloc.
class InfallibleAllocPolicy {
public:
template <
typename T>
T* pod_malloc(size_t aNumElems) {
if (aNumElems & mozilla::tl::MulOverflowMask<
sizeof(T)>::value) {
MOZ_CRASH(
"TestSegmentedVector.cpp: overflow");
}
T* rv =
static_cast<T*>(malloc(aNumElems *
sizeof(T)));
if (!rv) {
MOZ_CRASH(
"TestSegmentedVector.cpp: out of memory");
}
return rv;
}
template <
typename T>
void free_(T* aPtr, size_t aNumElems =
0) {
free(aPtr);
}
};
template <
typename Vector>
void CheckContents(Vector& vector, size_t expectedLength) {
MOZ_RELEASE_ASSERT(vector.Length() == expectedLength);
size_t n =
0;
for (
auto iter = vector.Iter(); !iter.Done(); iter.Next()) {
MOZ_RELEASE_ASSERT(iter.Get() ==
int(n));
n++;
}
MOZ_RELEASE_ASSERT(n == expectedLength);
}
// We want to test Append(), which is fallible and marked with
// [[nodiscard]]. But we're using an infallible alloc policy, and so
// don't really need to check the result. Casting to |void| works with clang
// but not GCC, so we instead use this dummy variable which works with both
// compilers.
static int gDummy;
// This tests basic segmented vector construction and iteration.
void TestBasics() {
// A SegmentedVector with a POD element type.
typedef SegmentedVector<
int,
1024, InfallibleAllocPolicy> MyVector;
MyVector v;
int i;
MOZ_RELEASE_ASSERT(v.IsEmpty());
// Add 100 elements, then check various things.
i =
0;
for (; i <
100; i++) {
gDummy = v.Append(std::move(i));
}
MOZ_RELEASE_ASSERT(!v.IsEmpty());
CheckContents(v,
100);
// Add another 900 elements, then re-check.
for (; i <
1000; i++) {
v.InfallibleAppend(std::move(i));
}
MOZ_RELEASE_ASSERT(!v.IsEmpty());
CheckContents(v,
1000);
// Pop off all of the elements.
MOZ_RELEASE_ASSERT(v.Length() ==
1000);
for (
int len = (
int)v.Length(); len >
0; len--) {
MOZ_RELEASE_ASSERT(v.GetLast() == len -
1);
v.PopLast();
}
MOZ_RELEASE_ASSERT(v.IsEmpty());
MOZ_RELEASE_ASSERT(v.Length() ==
0);
// Fill the vector up again to prepare for the clear.
for (i =
0; i <
1000; i++) {
v.InfallibleAppend(std::move(i));
}
MOZ_RELEASE_ASSERT(!v.IsEmpty());
MOZ_RELEASE_ASSERT(v.Length() ==
1000);
v.Clear();
MOZ_RELEASE_ASSERT(v.IsEmpty());
MOZ_RELEASE_ASSERT(v.Length() ==
0);
// Fill the vector up to verify PopLastN works.
for (i =
0; i <
1000; ++i) {
v.InfallibleAppend(std::move(i));
}
MOZ_RELEASE_ASSERT(!v.IsEmpty());
MOZ_RELEASE_ASSERT(v.Length() ==
1000);
// Verify we pop the right amount of elements.
v.PopLastN(
300);
MOZ_RELEASE_ASSERT(v.Length() ==
700);
// Verify the contents are what we expect.
CheckContents(v,
700);
}
void TestMoveAndSwap() {
typedef SegmentedVector<
int,
32, InfallibleAllocPolicy> MyVector;
MyVector v;
for (
int i =
0; i <
100; i++) {
(
void)v.Append(i);
}
MOZ_RELEASE_ASSERT(!v.IsEmpty());
CheckContents(v,
100);
// Test move constructor.
MyVector w(std::move(v));
CheckContents(w,
100);
MOZ_RELEASE_ASSERT(v.IsEmpty());
// Test move assignment.
v = std::move(w);
CheckContents(v,
100);
MOZ_RELEASE_ASSERT(w.IsEmpty());
// Test swap.
std::swap(v, w);
CheckContents(w,
100);
MOZ_RELEASE_ASSERT(v.IsEmpty());
}
static size_t gNumDefaultCtors;
static size_t gNumExplicitCtors;
static size_t gNumCopyCtors;
static size_t gNumMoveCtors;
static size_t gNumDtors;
struct NonPOD {
NonPOD() { gNumDefaultCtors++; }
explicit NonPOD(
int x) { gNumExplicitCtors++; }
NonPOD(NonPOD&) { gNumCopyCtors++; }
NonPOD(NonPOD&&) { gNumMoveCtors++; }
~NonPOD() { gNumDtors++; }
};
// This tests how segmented vectors with non-POD elements construct and
// destruct those elements.
void TestConstructorsAndDestructors() {
size_t defaultCtorCalls =
0;
size_t explicitCtorCalls =
0;
size_t copyCtorCalls =
0;
size_t moveCtorCalls =
0;
size_t dtorCalls =
0;
{
static const size_t segmentSize =
64;
// A SegmentedVector with a non-POD element type.
NonPOD x(
1);
// explicit constructor called
explicitCtorCalls++;
SegmentedVector<NonPOD, segmentSize, InfallibleAllocPolicy> v;
// default constructor called 0 times
MOZ_RELEASE_ASSERT(v.IsEmpty());
gDummy = v.Append(x);
// copy constructor called
copyCtorCalls++;
NonPOD y(
1);
// explicit constructor called
explicitCtorCalls++;
gDummy = v.Append(std::move(y));
// move constructor called
moveCtorCalls++;
NonPOD z(
1);
// explicit constructor called
explicitCtorCalls++;
v.InfallibleAppend(std::move(z));
// move constructor called
moveCtorCalls++;
v.PopLast();
// destructor called 1 time
dtorCalls++;
MOZ_RELEASE_ASSERT(gNumDtors == dtorCalls);
v.Clear();
// destructor called 2 times
dtorCalls +=
2;
// Test that PopLastN() correctly calls the destructors of all the
// elements in the segments it destroys.
//
// We depend on the size of NonPOD when determining how many things
// to push onto the vector. It would be nicer to get this information
// from SegmentedVector itself...
static_assert(
sizeof(NonPOD) ==
1,
"Fix length calculations!");
size_t nonFullLastSegmentSize = segmentSize -
1;
for (size_t i =
0; i < nonFullLastSegmentSize; ++i) {
gDummy = v.Append(x);
// copy constructor called
copyCtorCalls++;
}
MOZ_RELEASE_ASSERT(gNumCopyCtors == copyCtorCalls);
// Pop some of the elements.
{
size_t partialPopAmount =
5;
MOZ_RELEASE_ASSERT(nonFullLastSegmentSize > partialPopAmount);
v.PopLastN(partialPopAmount);
// destructor called partialPopAmount times
dtorCalls += partialPopAmount;
MOZ_RELEASE_ASSERT(v.Length() ==
nonFullLastSegmentSize - partialPopAmount);
MOZ_RELEASE_ASSERT(!v.IsEmpty());
MOZ_RELEASE_ASSERT(gNumDtors == dtorCalls);
}
// Pop a full segment.
{
size_t length = v.Length();
v.PopLastN(length);
dtorCalls += length;
// These two tests *are* semantically different given the underlying
// implementation; Length sums up the sizes of the internal segments,
// while IsEmpty looks at the sequence of internal segments.
MOZ_RELEASE_ASSERT(v.Length() ==
0);
MOZ_RELEASE_ASSERT(v.IsEmpty());
MOZ_RELEASE_ASSERT(gNumDtors == dtorCalls);
}
size_t multipleSegmentsSize = (segmentSize *
3) /
2;
for (size_t i =
0; i < multipleSegmentsSize; ++i) {
gDummy = v.Append(x);
// copy constructor called
copyCtorCalls++;
}
MOZ_RELEASE_ASSERT(gNumCopyCtors == copyCtorCalls);
// Pop across segment boundaries.
{
v.PopLastN(segmentSize);
dtorCalls += segmentSize;
MOZ_RELEASE_ASSERT(v.Length() == (multipleSegmentsSize - segmentSize));
MOZ_RELEASE_ASSERT(!v.IsEmpty());
MOZ_RELEASE_ASSERT(gNumDtors == dtorCalls);
}
// Clear everything here to make calculations easier.
{
size_t length = v.Length();
v.Clear();
dtorCalls += length;
MOZ_RELEASE_ASSERT(v.IsEmpty());
MOZ_RELEASE_ASSERT(gNumDtors == dtorCalls);
}
MOZ_RELEASE_ASSERT(gNumDefaultCtors == defaultCtorCalls);
MOZ_RELEASE_ASSERT(gNumExplicitCtors == explicitCtorCalls);
MOZ_RELEASE_ASSERT(gNumCopyCtors == copyCtorCalls);
MOZ_RELEASE_ASSERT(gNumMoveCtors == moveCtorCalls);
MOZ_RELEASE_ASSERT(gNumDtors == dtorCalls);
}
// destructor called for x, y, z
dtorCalls +=
3;
MOZ_RELEASE_ASSERT(gNumDtors == dtorCalls);
}
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 {};
// This tests that we get the right segment capacities for specified segment
// sizes, and that the elements are aligned appropriately.
void TestSegmentCapacitiesAndAlignments() {
// 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.
//
// Also, SegmentedVector has a static assertion that elements are
// appropriately aligned.
SegmentedVector<
double,
512> v1(
512);
SegmentedVector<A,
1024> v2(
1024);
SegmentedVector<B,
999> v3(
999);
SegmentedVector<C,
10> v4(
10);
SegmentedVector<D,
1234> v5(
1234);
SegmentedVector<E> v6(
4096);
// 4096 is the default segment size
SegmentedVector<mozilla::AlignedElem<
16>,
100> v7(
100);
}
void TestIterator() {
SegmentedVector<
int,
4> v;
auto iter = v.Iter();
auto iterFromLast = v.IterFromLast();
MOZ_RELEASE_ASSERT(iter.Done());
MOZ_RELEASE_ASSERT(iterFromLast.Done());
gDummy = v.Append(
1);
iter = v.Iter();
iterFromLast = v.IterFromLast();
MOZ_RELEASE_ASSERT(!iter.Done());
MOZ_RELEASE_ASSERT(!iterFromLast.Done());
iter.Next();
MOZ_RELEASE_ASSERT(iter.Done());
iterFromLast.Next();
MOZ_RELEASE_ASSERT(iterFromLast.Done());
iter = v.Iter();
iterFromLast = v.IterFromLast();
MOZ_RELEASE_ASSERT(!iter.Done());
MOZ_RELEASE_ASSERT(!iterFromLast.Done());
iter.Prev();
MOZ_RELEASE_ASSERT(iter.Done());
iterFromLast.Prev();
MOZ_RELEASE_ASSERT(iterFromLast.Done());
// Append enough entries to ensure we have at least two segments.
gDummy = v.Append(
1);
gDummy = v.Append(
1);
gDummy = v.Append(
1);
gDummy = v.Append(
1);
iter = v.Iter();
iterFromLast = v.IterFromLast();
MOZ_RELEASE_ASSERT(!iter.Done());
MOZ_RELEASE_ASSERT(!iterFromLast.Done());
iter.Prev();
MOZ_RELEASE_ASSERT(iter.Done());
iterFromLast.Next();
MOZ_RELEASE_ASSERT(iterFromLast.Done());
iter = v.Iter();
iterFromLast = v.IterFromLast();
MOZ_RELEASE_ASSERT(!iter.Done());
MOZ_RELEASE_ASSERT(!iterFromLast.Done());
iter.Next();
MOZ_RELEASE_ASSERT(!iter.Done());
iterFromLast.Prev();
MOZ_RELEASE_ASSERT(!iterFromLast.Done());
iter = v.Iter();
iterFromLast = v.IterFromLast();
int count =
0;
for (; !iter.Done() && !iterFromLast.Done();
iter.Next(), iterFromLast.Prev()) {
++count;
}
MOZ_RELEASE_ASSERT(count ==
5);
// Modify the vector while using the iterator.
iterFromLast = v.IterFromLast();
gDummy = v.Append(
2);
gDummy = v.Append(
3);
gDummy = v.Append(
4);
iterFromLast.Next();
MOZ_RELEASE_ASSERT(!iterFromLast.Done());
MOZ_RELEASE_ASSERT(iterFromLast.Get() ==
2);
iterFromLast.Next();
MOZ_RELEASE_ASSERT(iterFromLast.Get() ==
3);
iterFromLast.Next();
MOZ_RELEASE_ASSERT(iterFromLast.Get() ==
4);
iterFromLast.Next();
MOZ_RELEASE_ASSERT(iterFromLast.Done());
}
int main(
void) {
TestBasics();
TestMoveAndSwap();
TestConstructorsAndDestructors();
TestSegmentCapacitiesAndAlignments();
TestIterator();
return 0;
}