// Copyright 2016 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include #include #include #include #include namespace { enum class ConstructType { DEFAULT, LVALUE_REF, RVALUE_REF, L_THEN_R_REF, }; // Test objects. class TestBase { public: // Various constructor forms to exercise SlabAllocator::New TestBase() : ctype_(ConstructType::DEFAULT) { ++allocated_obj_count_; } explicit TestBase(const size_t&) : ctype_(ConstructType::LVALUE_REF) { ++allocated_obj_count_; } explicit TestBase(size_t&&) : ctype_(ConstructType::RVALUE_REF) { ++allocated_obj_count_; } explicit TestBase(const size_t&, size_t&&) : ctype_(ConstructType::L_THEN_R_REF) { ++allocated_obj_count_; } virtual ~TestBase() { --allocated_obj_count_; } ConstructType ctype() const { return ctype_; } static void Reset() { allocated_obj_count_ = 0; } static size_t allocated_obj_count() { return allocated_obj_count_; } const uint8_t* payload() const { return payload_; } private: const ConstructType ctype_; uint8_t payload_[13]; // 13 bytes, just to make the size/alignment strange static size_t allocated_obj_count_; }; // Static storage. size_t TestBase::allocated_obj_count_; template struct ReleaseHelper; template struct ReleaseHelper::type> { static void ReleasePtr(fbl::SlabAllocator& allocator, typename SATraits::PtrType& ptr) { // Instanced slab allocators should static_assert if you attempt to // expand their delete method. #if TEST_WILL_NOT_COMPILE || 0 allocator.Delete(ptr); #else delete ptr; #endif } }; template struct ReleaseHelper::type> { static void ReleasePtr(fbl::SlabAllocator& allocator, typename SATraits::PtrType& ptr) { // SlabAllocated<> objects which come from MANUAL_DELETE flavors of slab // allocators should have their delete operator protected in order to // prevent someone from calling delete on the object. #if TEST_WILL_NOT_COMPILE || 0 delete ptr; #else allocator.Delete(ptr); #endif } }; template struct ReleaseHelper::type> { static void ReleasePtr(fbl::SlabAllocator&, typename SATraits::PtrType& ptr) { ptr = nullptr; } }; template struct ReleaseHelper::type> { static void ReleasePtr(typename SATraits::PtrType& ptr) { delete ptr; } }; template struct ReleaseHelper::type> { static void ReleasePtr(typename SATraits::PtrType& ptr) { ptr = nullptr; } }; // Traits which define the various test flavors. template struct UnmanagedTestTraits { class ObjType; using PtrType = ObjType*; using AllocTraits = fbl::SlabAllocatorTraits ; using AllocatorType = fbl::SlabAllocator; using RefList = fbl::DoublyLinkedList; class ObjType : public TestBase, public fbl::SlabAllocated, public fbl::DoublyLinkedListable { public: ObjType() : TestBase() { } explicit ObjType(const size_t& val) : TestBase(val) { } explicit ObjType(size_t&& val) : TestBase(std::move(val)) { } explicit ObjType(const size_t& a, size_t&& b) : TestBase(a, std::move(b)) { } }; static constexpr size_t MaxSlabs = 4; static constexpr bool IsManaged = false; static constexpr size_t MaxAllocs(size_t slabs) { return AllocatorType::AllocsPerSlab * slabs; } }; template struct UniquePtrTestTraits { class ObjType; using PtrType = fbl::unique_ptr; using AllocTraits = fbl::SlabAllocatorTraits ; using AllocatorType = fbl::SlabAllocator; using RefList = fbl::DoublyLinkedList; class ObjType : public TestBase, public fbl::SlabAllocated, public fbl::DoublyLinkedListable { public: ObjType() : TestBase() { } explicit ObjType(const size_t& val) : TestBase(val) { } explicit ObjType(size_t&& val) : TestBase(std::move(val)) { } explicit ObjType(const size_t& a, size_t&& b) : TestBase(a, std::move(b)) { } }; static constexpr size_t MaxSlabs = 4; static constexpr bool IsManaged = true; static constexpr size_t MaxAllocs(size_t slabs) { return AllocatorType::AllocsPerSlab * slabs; } }; template struct RefPtrTestTraits { class ObjType; using PtrType = fbl::RefPtr; using AllocTraits = fbl::SlabAllocatorTraits ; using AllocatorType = fbl::SlabAllocator; using RefList = fbl::DoublyLinkedList; class ObjType : public TestBase, public fbl::RefCounted, public fbl::SlabAllocated, public fbl::DoublyLinkedListable { public: ObjType() : TestBase() { } explicit ObjType(const size_t& val) : TestBase(val) { } explicit ObjType(size_t&& val) : TestBase(std::move(val)) { } explicit ObjType(const size_t& a, size_t&& b) : TestBase(a, std::move(b)) { } }; static constexpr size_t MaxSlabs = 4; static constexpr bool IsManaged = true; static constexpr size_t MaxAllocs(size_t slabs) { return AllocatorType::AllocsPerSlab * slabs; } }; template struct ObjCounterHelper; template struct ObjCounterHelper { static bool CheckObjCount(const SA& allocator, size_t expected) { return (allocator.obj_count() == expected); } static bool CheckMaxObjCount(const SA& allocator, size_t expected) { return (allocator.max_obj_count() == expected); } static void ResetMaxObjCount(SA* allocator) { allocator->ResetMaxObjCount(); } static bool StaticCheckObjCount(size_t expected) { return (SA::obj_count() == expected); } static bool StaticCheckMaxObjCount(size_t expected) { return (SA::max_obj_count() == expected); } static void StaticResetMaxObjCount() { SA::ResetMaxObjCount(); } }; template struct ObjCounterHelper { static bool CheckObjCount(const SA&, size_t) { return true; } static bool CheckMaxObjCount(const SA&, size_t) { return true; } static void ResetMaxObjCount(SA*) {} static bool StaticCheckObjCount(size_t) { return true; } static bool StaticCheckMaxObjCount(size_t) { return true; } static void StaticResetMaxObjCount() {} }; template bool do_slab_test(typename Traits::AllocatorType& allocator, size_t test_allocs) { BEGIN_TEST; const size_t MAX_ALLOCS = Traits::MaxAllocs(allocator.max_slabs()); typename Traits::RefList ref_list; const bool ENB_OBJ_COUNT = Traits::AllocTraits::ENABLE_OBJ_COUNT; using AllocatorType = typename Traits::AllocatorType; ObjCounterHelper::ResetMaxObjCount(&allocator); bool res = ObjCounterHelper::CheckObjCount(allocator, 0); EXPECT_TRUE(res); res = ObjCounterHelper::CheckMaxObjCount(allocator, 0); EXPECT_TRUE(res); // Allocate up to the test limit. for (size_t i = 0; i < test_allocs; ++i) { typename Traits::PtrType ptr; EXPECT_EQ(fbl::min(i, MAX_ALLOCS), TestBase::allocated_obj_count()); res = ObjCounterHelper::CheckObjCount (allocator,TestBase::allocated_obj_count()); EXPECT_TRUE(res); res = ObjCounterHelper::CheckMaxObjCount (allocator, TestBase::allocated_obj_count()); EXPECT_TRUE(res); // Allocate the object; exercise the various constructors switch (i % 4) { case 0: ptr = allocator.New(); break; case 1: ptr = allocator.New(i); break; case 2: ptr = allocator.New(std::move(i)); break; case 3: ptr = allocator.New(i, std::move(i)); break; } if (i < MAX_ALLOCS) { ASSERT_NONNULL(ptr, "Allocation failed when it should not have!"); ref_list.push_front(std::move(ptr)); } else { ASSERT_NULL(ptr, "Allocation succeeded when it should not have!"); } EXPECT_EQ(fbl::min(i + 1, MAX_ALLOCS), TestBase::allocated_obj_count()); res = ObjCounterHelper::CheckObjCount (allocator, TestBase::allocated_obj_count()); EXPECT_TRUE(res); res = ObjCounterHelper::CheckMaxObjCount (allocator, TestBase::allocated_obj_count()); EXPECT_TRUE(res); } // Now remove and de-allocate. size_t max_obj_count = TestBase::allocated_obj_count(); size_t i; for (i = 0; !ref_list.is_empty(); ++i) { auto ptr = ref_list.pop_back(); ASSERT_NONNULL(ptr, "nullptr in ref list! This should be impossible."); EXPECT_EQ(fbl::min(test_allocs, MAX_ALLOCS) - i, TestBase::allocated_obj_count()); res = ObjCounterHelper::CheckObjCount (allocator, TestBase::allocated_obj_count()); EXPECT_TRUE(res); res = ObjCounterHelper::CheckMaxObjCount (allocator, max_obj_count); EXPECT_TRUE(res); switch (i % 4) { case 0: EXPECT_EQ(ConstructType::DEFAULT, ptr->ctype()); break; case 1: EXPECT_EQ(ConstructType::LVALUE_REF, ptr->ctype()); break; case 2: EXPECT_EQ(ConstructType::RVALUE_REF, ptr->ctype()); break; case 3: EXPECT_EQ(ConstructType::L_THEN_R_REF, ptr->ctype()); break; } // Release the reference (how this gets done depends on allocator flavor and pointer type) ReleaseHelper::ReleasePtr(allocator, ptr); if (i % 2 == 1) { ObjCounterHelper::ResetMaxObjCount(&allocator); max_obj_count = TestBase::allocated_obj_count(); } res = ObjCounterHelper::CheckMaxObjCount (allocator, max_obj_count); EXPECT_TRUE(res); } EXPECT_EQ(fbl::min(test_allocs, MAX_ALLOCS), i); res = ObjCounterHelper::CheckObjCount (allocator, 0); EXPECT_TRUE(res); res = ObjCounterHelper::CheckMaxObjCount (allocator, i % 2); EXPECT_TRUE(res); ObjCounterHelper::ResetMaxObjCount(&allocator); res = ObjCounterHelper::CheckMaxObjCount (allocator, 0); EXPECT_TRUE(res); #if TEST_WILL_NOT_COMPILE || 0 allocator.obj_count(); #endif #if TEST_WILL_NOT_COMPILE || 0 allocator.max_obj_count(); #endif #if TEST_WILL_NOT_COMPILE || 0 allocator.ResetMaxObjCount(); #endif END_TEST; } template bool slab_test() { BEGIN_TEST; typename Traits::AllocatorType allocator(SlabCount); TestBase::Reset(); EXPECT_TRUE(do_slab_test(allocator, 1), "Single allocator test failed"); EXPECT_TRUE(do_slab_test(allocator, Traits::MaxAllocs(SlabCount) / 2), "1/2 capacity allocator test failed"); EXPECT_TRUE(do_slab_test(allocator, Traits::MaxAllocs(SlabCount) + 4), "Over-capacity allocator test failed"); END_TEST; } template struct StaticUnmanagedTestTraits { class ObjType; using PtrType = ObjType*; using AllocTraits = fbl::StaticSlabAllocatorTraits; using AllocatorType = fbl::SlabAllocator; using RefList = fbl::DoublyLinkedList; class ObjType : public TestBase, public fbl::SlabAllocated, public fbl::DoublyLinkedListable { public: ObjType() : TestBase() { } explicit ObjType(const size_t& val) : TestBase(val) { } explicit ObjType(size_t&& val) : TestBase(std::move(val)) { } explicit ObjType(const size_t& a, size_t&& b) : TestBase(a, std::move(b)) { } }; static size_t MaxAllocs() { return AllocatorType::AllocsPerSlab * AllocatorType::max_slabs(); } static constexpr size_t MaxSlabs = 4; static constexpr bool IsManaged = false; }; template using StaticCountedUnmanagedTestTraits = StaticUnmanagedTestTraits; template struct StaticUniquePtrTestTraits { class ObjType; using PtrType = fbl::unique_ptr; using AllocTraits = fbl::StaticSlabAllocatorTraits; using AllocatorType = fbl::SlabAllocator; using RefList = fbl::DoublyLinkedList; class ObjType : public TestBase, public fbl::SlabAllocated, public fbl::DoublyLinkedListable { public: ObjType() : TestBase() { } explicit ObjType(const size_t& val) : TestBase(val) { } explicit ObjType(size_t&& val) : TestBase(std::move(val)) { } explicit ObjType(const size_t& a, size_t&& b) : TestBase(a, std::move(b)) { } }; static size_t MaxAllocs() { return AllocatorType::AllocsPerSlab * AllocatorType::max_slabs(); } static constexpr size_t MaxSlabs = 4; static constexpr bool IsManaged = false; }; template using StaticCountedUniquePtrTestTraits = StaticUniquePtrTestTraits; template struct StaticRefPtrTestTraits { class ObjType; using PtrType = fbl::RefPtr; using AllocTraits = fbl::StaticSlabAllocatorTraits; using AllocatorType = fbl::SlabAllocator; using RefList = fbl::DoublyLinkedList; class ObjType : public TestBase, public fbl::SlabAllocated, public fbl::RefCounted, public fbl::DoublyLinkedListable { public: ObjType() : TestBase() { } explicit ObjType(const size_t& val) : TestBase(val) { } explicit ObjType(size_t&& val) : TestBase(std::move(val)) { } explicit ObjType(const size_t& a, size_t&& b) : TestBase(a, std::move(b)) { } }; static constexpr size_t MaxSlabs = 4; static constexpr bool IsManaged = false; static size_t MaxAllocs() { return AllocatorType::AllocsPerSlab * AllocatorType::max_slabs(); } }; template using StaticCountedRefPtrTestTraits = StaticRefPtrTestTraits; template bool do_static_slab_test(size_t test_allocs) { BEGIN_TEST; const bool ENB_OBJ_COUNT = Traits::AllocTraits::ENABLE_OBJ_COUNT; using AllocatorType = typename Traits::AllocatorType; const size_t MAX_ALLOCS = Traits::MaxAllocs(); typename Traits::RefList ref_list; ObjCounterHelper::StaticResetMaxObjCount(); bool res = ObjCounterHelper::StaticCheckObjCount(0); EXPECT_TRUE(res); res = ObjCounterHelper::StaticCheckMaxObjCount(0); EXPECT_TRUE(res); // Allocate up to the test limit. for (size_t i = 0; i < test_allocs; ++i) { typename Traits::PtrType ptr; EXPECT_EQ(fbl::min(i, MAX_ALLOCS), TestBase::allocated_obj_count()); res = ObjCounterHelper::StaticCheckObjCount (TestBase::allocated_obj_count()); EXPECT_TRUE(res); res = ObjCounterHelper::StaticCheckMaxObjCount (TestBase::allocated_obj_count()); EXPECT_TRUE(res); // Allocate the object; exercise the various constructors switch (i % 4) { case 0: ptr = AllocatorType::New(); break; case 1: ptr = AllocatorType::New(i); break; case 2: ptr = AllocatorType::New(std::move(i)); break; case 3: ptr = AllocatorType::New(i, std::move(i)); break; } if (i < MAX_ALLOCS) { ASSERT_NONNULL(ptr, "Allocation failed when it should not have!"); ref_list.push_front(std::move(ptr)); } else { ASSERT_NULL(ptr, "Allocation succeeded when it should not have!"); } EXPECT_EQ(fbl::min(i + 1, MAX_ALLOCS), TestBase::allocated_obj_count()); res = ObjCounterHelper::StaticCheckObjCount (TestBase::allocated_obj_count()); EXPECT_TRUE(res); res = ObjCounterHelper::StaticCheckMaxObjCount (TestBase::allocated_obj_count()); EXPECT_TRUE(res); } // Now remove and de-allocate. size_t max_obj_count = TestBase::allocated_obj_count(); size_t i; for (i = 0; !ref_list.is_empty(); ++i) { auto ptr = ref_list.pop_back(); ASSERT_NONNULL(ptr, "nullptr in ref list! This should be impossible."); EXPECT_EQ(fbl::min(test_allocs, MAX_ALLOCS) - i, TestBase::allocated_obj_count()); res = ObjCounterHelper::StaticCheckObjCount (TestBase::allocated_obj_count()); EXPECT_TRUE(res); res = ObjCounterHelper::StaticCheckMaxObjCount(max_obj_count); EXPECT_TRUE(res); switch (i % 4) { case 0: EXPECT_EQ(ConstructType::DEFAULT, ptr->ctype()); break; case 1: EXPECT_EQ(ConstructType::LVALUE_REF, ptr->ctype()); break; case 2: EXPECT_EQ(ConstructType::RVALUE_REF, ptr->ctype()); break; case 3: EXPECT_EQ(ConstructType::L_THEN_R_REF, ptr->ctype()); break; } // Release the reference (how this gets done depends on allocator flavor and pointer type) ReleaseHelper::ReleasePtr(ptr); if (i % 2 == 1) { ObjCounterHelper::StaticResetMaxObjCount(); max_obj_count = TestBase::allocated_obj_count(); } res = ObjCounterHelper::StaticCheckMaxObjCount(max_obj_count); EXPECT_TRUE(res); } EXPECT_EQ(fbl::min(test_allocs, MAX_ALLOCS), i); res = ObjCounterHelper::StaticCheckObjCount(0); EXPECT_TRUE(res); res = ObjCounterHelper::StaticCheckMaxObjCount(i % 2); EXPECT_TRUE(res); ObjCounterHelper::StaticResetMaxObjCount(); res = ObjCounterHelper::StaticCheckMaxObjCount(0); EXPECT_TRUE(res); #if TEST_WILL_NOT_COMPILE || 0 AllocatorType::obj_count(); #endif #if TEST_WILL_NOT_COMPILE || 0 AllocatorType::max_obj_count(); #endif #if TEST_WILL_NOT_COMPILE || 0 AllocatorType::ResetMaxObjCount(); #endif END_TEST; } template bool static_slab_test() { BEGIN_TEST; TestBase::Reset(); EXPECT_TRUE(do_static_slab_test(1), "Single allocator test failed"); EXPECT_TRUE(do_static_slab_test(Traits::MaxAllocs() / 2), "1/2 capacity allocator test failed"); EXPECT_TRUE(do_static_slab_test(Traits::MaxAllocs() + 4), "Over-capacity allocator test failed"); END_TEST; } } // anon namespace using MutexLock = ::fbl::Mutex; using NullLock = ::fbl::NullLock; DECLARE_STATIC_SLAB_ALLOCATOR_STORAGE(StaticUnmanagedTestTraits::AllocTraits, 1); DECLARE_STATIC_SLAB_ALLOCATOR_STORAGE(StaticUniquePtrTestTraits::AllocTraits, 1); DECLARE_STATIC_SLAB_ALLOCATOR_STORAGE(StaticRefPtrTestTraits::AllocTraits, 1); DECLARE_STATIC_SLAB_ALLOCATOR_STORAGE(StaticUnmanagedTestTraits::AllocTraits, 1); DECLARE_STATIC_SLAB_ALLOCATOR_STORAGE(StaticUniquePtrTestTraits::AllocTraits, 1); DECLARE_STATIC_SLAB_ALLOCATOR_STORAGE(StaticRefPtrTestTraits::AllocTraits, 1); DECLARE_STATIC_SLAB_ALLOCATOR_STORAGE(StaticCountedUnmanagedTestTraits::AllocTraits, 1); DECLARE_STATIC_SLAB_ALLOCATOR_STORAGE(StaticCountedUniquePtrTestTraits::AllocTraits, 1); DECLARE_STATIC_SLAB_ALLOCATOR_STORAGE(StaticCountedRefPtrTestTraits::AllocTraits, 1); DECLARE_STATIC_SLAB_ALLOCATOR_STORAGE(StaticCountedUnmanagedTestTraits::AllocTraits, 1); DECLARE_STATIC_SLAB_ALLOCATOR_STORAGE(StaticCountedUniquePtrTestTraits::AllocTraits, 1); DECLARE_STATIC_SLAB_ALLOCATOR_STORAGE(StaticCountedRefPtrTestTraits::AllocTraits, 1); BEGIN_TEST_CASE(slab_allocator_tests) RUN_NAMED_TEST("Unmanaged Single Slab (mutex)", (slab_test, 1>)) RUN_NAMED_TEST("Unmanaged Multi Slab (mutex)", (slab_test>)) RUN_NAMED_TEST("UniquePtr Single Slab (mutex)", (slab_test, 1>)) RUN_NAMED_TEST("UniquePtr Multi Slab (mutex)", (slab_test>)) RUN_NAMED_TEST("RefPtr Single Slab (mutex)", (slab_test, 1>)) RUN_NAMED_TEST("RefPtr Multi Slab (mutex)", (slab_test>)) RUN_NAMED_TEST("Unmanaged Single Slab (unlock)", (slab_test, 1>)) RUN_NAMED_TEST("Unmanaged Multi Slab (unlock)", (slab_test>)) RUN_NAMED_TEST("UniquePtr Single Slab (unlock)", (slab_test, 1>)) RUN_NAMED_TEST("UniquePtr Multi Slab (unlock)", (slab_test>)) RUN_NAMED_TEST("RefPtr Single Slab (unlock)", (slab_test, 1>)) RUN_NAMED_TEST("RefPtr Multi Slab (unlock)", (slab_test>)) RUN_NAMED_TEST("Manual Delete Unmanaged (mutex)", (slab_test>)) RUN_NAMED_TEST("Manual Delete Unmanaged (unlock)", (slab_test>)) RUN_NAMED_TEST("Static Unmanaged (unlock)", (static_slab_test>)) RUN_NAMED_TEST("Static UniquePtr (unlock)", (static_slab_test>)) RUN_NAMED_TEST("Static RefPtr (unlock)", (static_slab_test>)) RUN_NAMED_TEST("Static Unmanaged (mutex)", (static_slab_test>)) RUN_NAMED_TEST("Static UniquePtr (mutex)", (static_slab_test>)) RUN_NAMED_TEST("Static RefPtr (mutex)", (static_slab_test>)) RUN_NAMED_TEST("Static Unmanaged (unlock)", (static_slab_test>)) RUN_NAMED_TEST("Static UniquePtr (unlock)", (static_slab_test>)) RUN_NAMED_TEST("Static RefPtr (unlock)", (static_slab_test>)) RUN_NAMED_TEST("Counted Unmanaged Single Slab (mutex)",(slab_test , 1>)) RUN_NAMED_TEST("Counted Unmanaged Multi Slab (mutex)", (slab_test >)) RUN_NAMED_TEST("Counted UniquePtr Single Slab (mutex)", (slab_test , 1>)) RUN_NAMED_TEST("Counted UniquePtr Multi Slab (mutex)", (slab_test >)) RUN_NAMED_TEST("Counted RefPtr Single Slab (mutex)", (slab_test , 1>)) RUN_NAMED_TEST("Counted RefPtr Multi Slab (mutex)", (slab_test >)) RUN_NAMED_TEST("Counted Unmanaged Single Slab (unlock)", (slab_test , 1>)) RUN_NAMED_TEST("Counted Unmanaged Multi Slab (unlock)", (slab_test >)) RUN_NAMED_TEST("Counted UniquePtr Single Slab (unlock)", (slab_test , 1>)) RUN_NAMED_TEST("Counted UniquePtr Multi Slab (unlock)", (slab_test >)) RUN_NAMED_TEST("Counted RefPtr Single Slab (unlock)", (slab_test , 1>)) RUN_NAMED_TEST("Counted RefPtr Multi Slab (unlock)", (slab_test >)) RUN_NAMED_TEST("Counted Manual Delete Unmanaged (mutex)", (slab_test >)) RUN_NAMED_TEST("Counted Manual Delete Unmanaged (unlock)", (slab_test >)) RUN_NAMED_TEST("Counted Static Unmanaged (unlock)", (static_slab_test >)) RUN_NAMED_TEST("Counted Static UniquePtr (unlock)", (static_slab_test >)) RUN_NAMED_TEST("Counted Static RefPtr (unlock)", (static_slab_test >)) RUN_NAMED_TEST("Counted Static Unmanaged (mutex)", (static_slab_test >)) RUN_NAMED_TEST("Counted Static UniquePtr (mutex)", (static_slab_test >)) RUN_NAMED_TEST("Counted Static RefPtr (mutex)", (static_slab_test >)) RUN_NAMED_TEST("Counted Static Unmanaged (unlock)", (static_slab_test >)) RUN_NAMED_TEST("Counted Static UniquePtr (unlock)", (static_slab_test >)) RUN_NAMED_TEST("Counted Static RefPtr (unlock)", (static_slab_test >)) END_TEST_CASE(slab_allocator_tests);