/* -*- 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 T* pod_malloc(size_t aNumElems) { if (aNumElems & mozilla::tl::MulOverflowMask::value) { MOZ_CRASH("TestSegmentedVector.cpp: overflow"); } T* rv = static_cast(malloc(aNumElems * sizeof(T))); if (!rv) { MOZ_CRASH("TestSegmentedVector.cpp: out of memory"); } return rv; } template void free_(T* aPtr, size_t aNumElems = 0) { free(aPtr); } }; template 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 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 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 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 v1(512); SegmentedVector v2(1024); SegmentedVector v3(999); SegmentedVector v4(10); SegmentedVector v5(1234); SegmentedVector v6(4096); // 4096 is the default segment size SegmentedVector, 100> v7(100); } void TestIterator() { SegmentedVector 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; }