// Copyright 2020 the V8 project 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 "src/heap/cppgc/free-list.h" #include #include #include #include "src/base/bits.h" #include "src/heap/cppgc/globals.h" #include "src/heap/cppgc/heap-object-header.h" #include "testing/gtest/include/gtest/gtest.h" namespace cppgc { namespace internal { namespace { class Block { public: Block() = default; explicit Block(size_t size) : address_(calloc(1, size)), size_(size) {} Block(Block&& other) V8_NOEXCEPT : address_(other.address_), size_(other.size_) { other.address_ = nullptr; other.size_ = 0; } Block& operator=(Block&& other) V8_NOEXCEPT { address_ = other.address_; size_ = other.size_; other.address_ = nullptr; other.size_ = 0; return *this; } ~Block() { free(address_); } void* Address() const { return address_; } size_t Size() const { return size_; } private: void* address_ = nullptr; size_t size_ = 0; }; std::vector CreateEntries() { static constexpr size_t kFreeListEntrySizeLog2 = v8::base::bits::WhichPowerOfTwo(kFreeListEntrySize); std::vector vector; vector.reserve(kPageSizeLog2); for (size_t i = kFreeListEntrySizeLog2; i < kPageSizeLog2; ++i) { vector.emplace_back(static_cast(1u) << i); } return vector; } FreeList CreatePopulatedFreeList(const std::vector& blocks) { FreeList list; for (const auto& block : blocks) { list.Add({block.Address(), block.Size()}); } return list; } } // namespace TEST(FreeListTest, Empty) { FreeList list; EXPECT_TRUE(list.IsEmpty()); EXPECT_EQ(0u, list.Size()); auto block = list.Allocate(16); EXPECT_EQ(nullptr, block.address); EXPECT_EQ(0u, block.size); } TEST(FreeListTest, Add) { auto blocks = CreateEntries(); FreeList list = CreatePopulatedFreeList(blocks); EXPECT_FALSE(list.IsEmpty()); const size_t allocated_size = std::accumulate( blocks.cbegin(), blocks.cend(), 0u, [](size_t acc, const Block& b) { return acc + b.Size(); }); EXPECT_EQ(allocated_size, list.Size()); } TEST(FreeListTest, AddWasted) { FreeList list; alignas(HeapObjectHeader) uint8_t buffer[sizeof(HeapObjectHeader)]; list.Add({buffer, sizeof(buffer)}); EXPECT_EQ(0u, list.Size()); EXPECT_TRUE(list.IsEmpty()); } TEST(FreeListTest, Clear) { auto blocks = CreateEntries(); FreeList list = CreatePopulatedFreeList(blocks); list.Clear(); EXPECT_EQ(0u, list.Size()); EXPECT_TRUE(list.IsEmpty()); } TEST(FreeListTest, Move) { { auto blocks = CreateEntries(); FreeList list1 = CreatePopulatedFreeList(blocks); const size_t expected_size = list1.Size(); FreeList list2 = std::move(list1); EXPECT_EQ(expected_size, list2.Size()); EXPECT_FALSE(list2.IsEmpty()); EXPECT_EQ(0u, list1.Size()); EXPECT_TRUE(list1.IsEmpty()); } { auto blocks1 = CreateEntries(); FreeList list1 = CreatePopulatedFreeList(blocks1); const size_t expected_size = list1.Size(); auto blocks2 = CreateEntries(); FreeList list2 = CreatePopulatedFreeList(blocks2); list2 = std::move(list1); EXPECT_EQ(expected_size, list2.Size()); EXPECT_FALSE(list2.IsEmpty()); EXPECT_EQ(0u, list1.Size()); EXPECT_TRUE(list1.IsEmpty()); } } TEST(FreeListTest, Append) { auto blocks1 = CreateEntries(); FreeList list1 = CreatePopulatedFreeList(blocks1); const size_t list1_size = list1.Size(); auto blocks2 = CreateEntries(); FreeList list2 = CreatePopulatedFreeList(blocks2); const size_t list2_size = list1.Size(); list2.Append(std::move(list1)); EXPECT_EQ(list1_size + list2_size, list2.Size()); EXPECT_FALSE(list2.IsEmpty()); EXPECT_EQ(0u, list1.Size()); EXPECT_TRUE(list1.IsEmpty()); } TEST(FreeListTest, Contains) { auto blocks = CreateEntries(); FreeList list = CreatePopulatedFreeList(blocks); for (const auto& block : blocks) { EXPECT_TRUE(list.ContainsForTesting({block.Address(), block.Size()})); } } TEST(FreeListTest, Allocate) { static constexpr size_t kFreeListEntrySizeLog2 = v8::base::bits::WhichPowerOfTwo(kFreeListEntrySize); std::vector blocks; blocks.reserve(kPageSizeLog2); for (size_t i = kFreeListEntrySizeLog2; i < kPageSizeLog2; ++i) { blocks.emplace_back(static_cast(1u) << i); } FreeList list = CreatePopulatedFreeList(blocks); // Try allocate from the biggest block. for (auto it = blocks.rbegin(); it < blocks.rend(); ++it) { const auto result = list.Allocate(it->Size()); EXPECT_EQ(it->Address(), result.address); EXPECT_EQ(it->Size(), result.size); } EXPECT_EQ(0u, list.Size()); EXPECT_TRUE(list.IsEmpty()); // Check that allocation fails for empty list: const auto empty_block = list.Allocate(8); EXPECT_EQ(nullptr, empty_block.address); EXPECT_EQ(0u, empty_block.size); } } // namespace internal } // namespace cppgc