| // 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 <memory> |
| #include <numeric> |
| #include <vector> |
| |
| #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<Block> CreateEntries() { |
| static constexpr size_t kFreeListEntrySizeLog2 = |
| v8::base::bits::WhichPowerOfTwo(kFreeListEntrySize); |
| std::vector<Block> vector; |
| vector.reserve(kPageSizeLog2); |
| for (size_t i = kFreeListEntrySizeLog2; i < kPageSizeLog2; ++i) { |
| vector.emplace_back(static_cast<size_t>(1u) << i); |
| } |
| return vector; |
| } |
| |
| FreeList CreatePopulatedFreeList(const std::vector<Block>& 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.Contains({block.Address(), block.Size()})); |
| } |
| } |
| |
| TEST(FreeListTest, Allocate) { |
| static constexpr size_t kFreeListEntrySizeLog2 = |
| v8::base::bits::WhichPowerOfTwo(kFreeListEntrySize); |
| |
| std::vector<Block> blocks; |
| blocks.reserve(kPageSizeLog2); |
| for (size_t i = kFreeListEntrySizeLog2; i < kPageSizeLog2; ++i) { |
| blocks.emplace_back(static_cast<size_t>(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 |