| // 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/heap-page.h" |
| |
| #include <algorithm> |
| |
| #include "include/cppgc/allocation.h" |
| #include "include/cppgc/persistent.h" |
| #include "src/base/macros.h" |
| #include "src/heap/cppgc/globals.h" |
| #include "src/heap/cppgc/heap-object-header.h" |
| #include "src/heap/cppgc/page-memory.h" |
| #include "src/heap/cppgc/raw-heap.h" |
| #include "test/unittests/heap/cppgc/tests.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace cppgc { |
| namespace internal { |
| |
| namespace { |
| |
| class PageTest : public testing::TestWithHeap { |
| public: |
| RawHeap& GetRawHeap() { return Heap::From(GetHeap())->raw_heap(); } |
| PageBackend* GetPageBackend() { |
| return Heap::From(GetHeap())->page_backend(); |
| } |
| }; |
| |
| template <size_t Size> |
| class GCed : public GarbageCollected<GCed<Size>> { |
| public: |
| virtual void Trace(cppgc::Visitor*) const {} |
| char array[Size]; |
| }; |
| |
| } // namespace |
| |
| TEST_F(PageTest, SpaceIndexing) { |
| RawHeap& heap = GetRawHeap(); |
| size_t space = 0u; |
| for (const auto& ptr : heap) { |
| EXPECT_EQ(&heap, ptr.get()->raw_heap()); |
| EXPECT_EQ(space, ptr->index()); |
| ++space; |
| } |
| EXPECT_GE(space, RawHeap::kNumberOfRegularSpaces); |
| } |
| |
| TEST_F(PageTest, PredefinedSpaces) { |
| using SpaceType = RawHeap::RegularSpaceType; |
| RawHeap& heap = GetRawHeap(); |
| { |
| auto* gced = MakeGarbageCollected<GCed<1>>(GetAllocationHandle()); |
| BaseSpace* space = NormalPage::FromPayload(gced)->space(); |
| EXPECT_EQ(heap.Space(SpaceType::kNormal1), space); |
| EXPECT_EQ(0u, space->index()); |
| EXPECT_FALSE(space->is_large()); |
| } |
| { |
| auto* gced = MakeGarbageCollected<GCed<32>>(GetAllocationHandle()); |
| BaseSpace* space = NormalPage::FromPayload(gced)->space(); |
| EXPECT_EQ(heap.Space(SpaceType::kNormal2), space); |
| EXPECT_EQ(1u, space->index()); |
| EXPECT_FALSE(space->is_large()); |
| } |
| { |
| auto* gced = MakeGarbageCollected<GCed<64>>(GetAllocationHandle()); |
| BaseSpace* space = NormalPage::FromPayload(gced)->space(); |
| EXPECT_EQ(heap.Space(SpaceType::kNormal3), space); |
| EXPECT_EQ(2u, space->index()); |
| EXPECT_FALSE(space->is_large()); |
| } |
| { |
| auto* gced = MakeGarbageCollected<GCed<128>>(GetAllocationHandle()); |
| BaseSpace* space = NormalPage::FromPayload(gced)->space(); |
| EXPECT_EQ(heap.Space(SpaceType::kNormal4), space); |
| EXPECT_EQ(3u, space->index()); |
| EXPECT_FALSE(space->is_large()); |
| } |
| { |
| auto* gced = MakeGarbageCollected<GCed<2 * kLargeObjectSizeThreshold>>( |
| GetAllocationHandle()); |
| BaseSpace* space = NormalPage::FromPayload(gced)->space(); |
| EXPECT_EQ(heap.Space(SpaceType::kLarge), space); |
| EXPECT_EQ(4u, space->index()); |
| EXPECT_TRUE(space->is_large()); |
| } |
| } |
| |
| TEST_F(PageTest, NormalPageIndexing) { |
| using SpaceType = RawHeap::RegularSpaceType; |
| constexpr size_t kExpectedNumberOfPages = 10u; |
| constexpr size_t kObjectSize = 8u; |
| using Type = GCed<kObjectSize>; |
| static const size_t kNumberOfObjects = |
| (kExpectedNumberOfPages * NormalPage::PayloadSize() / |
| (sizeof(Type) + sizeof(HeapObjectHeader))) - |
| kExpectedNumberOfPages; |
| |
| std::vector<Persistent<Type>> persistents(kNumberOfObjects); |
| for (auto& p : persistents) { |
| p = MakeGarbageCollected<Type>(GetAllocationHandle()); |
| } |
| |
| const RawHeap& heap = GetRawHeap(); |
| const BaseSpace* space = heap.Space(SpaceType::kNormal1); |
| EXPECT_EQ(kExpectedNumberOfPages, space->size()); |
| |
| size_t page_n = 0; |
| for (const BasePage* page : *space) { |
| EXPECT_FALSE(page->is_large()); |
| EXPECT_EQ(space, page->space()); |
| ++page_n; |
| } |
| EXPECT_EQ(page_n, space->size()); |
| } |
| |
| TEST_F(PageTest, LargePageIndexing) { |
| using SpaceType = RawHeap::RegularSpaceType; |
| constexpr size_t kExpectedNumberOfPages = 10u; |
| constexpr size_t kObjectSize = 2 * kLargeObjectSizeThreshold; |
| using Type = GCed<kObjectSize>; |
| const size_t kNumberOfObjects = kExpectedNumberOfPages; |
| |
| std::vector<Persistent<Type>> persistents(kNumberOfObjects); |
| for (auto& p : persistents) { |
| p = MakeGarbageCollected<Type>(GetAllocationHandle()); |
| } |
| |
| const RawHeap& heap = GetRawHeap(); |
| const BaseSpace* space = heap.Space(SpaceType::kLarge); |
| EXPECT_EQ(kExpectedNumberOfPages, space->size()); |
| |
| size_t page_n = 0; |
| for (const BasePage* page : *space) { |
| EXPECT_TRUE(page->is_large()); |
| ++page_n; |
| } |
| EXPECT_EQ(page_n, space->size()); |
| } |
| |
| TEST_F(PageTest, HeapObjectHeaderOnBasePageIndexing) { |
| constexpr size_t kObjectSize = 8; |
| using Type = GCed<kObjectSize>; |
| const size_t kNumberOfObjects = |
| NormalPage::PayloadSize() / (sizeof(Type) + sizeof(HeapObjectHeader)); |
| const size_t kLeftSpace = |
| NormalPage::PayloadSize() % (sizeof(Type) + sizeof(HeapObjectHeader)); |
| |
| std::vector<Persistent<Type>> persistents(kNumberOfObjects); |
| for (auto& p : persistents) { |
| p = MakeGarbageCollected<Type>(GetAllocationHandle()); |
| } |
| |
| const auto* page = |
| static_cast<NormalPage*>(BasePage::FromPayload(persistents[0].Get())); |
| size_t size = 0; |
| size_t num = 0; |
| for (const HeapObjectHeader& header : *page) { |
| EXPECT_EQ(reinterpret_cast<Address>(persistents[num].Get()), |
| header.Payload()); |
| size += header.GetSize(); |
| ++num; |
| } |
| EXPECT_EQ(num, persistents.size()); |
| EXPECT_EQ(size + kLeftSpace, NormalPage::PayloadSize()); |
| } |
| |
| TEST_F(PageTest, HeapObjectHeaderOnLargePageIndexing) { |
| constexpr size_t kObjectSize = 2 * kLargeObjectSizeThreshold; |
| using Type = GCed<kObjectSize>; |
| auto* gced = MakeGarbageCollected<Type>(GetAllocationHandle()); |
| |
| const auto* page = static_cast<LargePage*>(BasePage::FromPayload(gced)); |
| const size_t expected_payload_size = |
| RoundUp(sizeof(Type) + sizeof(HeapObjectHeader), kAllocationGranularity); |
| EXPECT_EQ(expected_payload_size, page->PayloadSize()); |
| |
| const HeapObjectHeader* header = page->ObjectHeader(); |
| EXPECT_EQ(reinterpret_cast<Address>(gced), header->Payload()); |
| } |
| |
| TEST_F(PageTest, NormalPageCreationDestruction) { |
| RawHeap& heap = GetRawHeap(); |
| const PageBackend* backend = Heap::From(GetHeap())->page_backend(); |
| auto* space = static_cast<NormalPageSpace*>( |
| heap.Space(RawHeap::RegularSpaceType::kNormal1)); |
| auto* page = NormalPage::Create(GetPageBackend(), space); |
| EXPECT_NE(nullptr, backend->Lookup(page->PayloadStart())); |
| |
| space->AddPage(page); |
| EXPECT_NE(space->end(), std::find(space->begin(), space->end(), page)); |
| |
| space->free_list().Add({page->PayloadStart(), page->PayloadSize()}); |
| EXPECT_TRUE( |
| space->free_list().Contains({page->PayloadStart(), page->PayloadSize()})); |
| |
| space->free_list().Clear(); |
| EXPECT_FALSE( |
| space->free_list().Contains({page->PayloadStart(), page->PayloadSize()})); |
| space->RemovePage(page); |
| EXPECT_EQ(space->end(), std::find(space->begin(), space->end(), page)); |
| NormalPage::Destroy(page); |
| EXPECT_EQ(nullptr, backend->Lookup(page->PayloadStart())); |
| } |
| |
| TEST_F(PageTest, LargePageCreationDestruction) { |
| constexpr size_t kObjectSize = 2 * kLargeObjectSizeThreshold; |
| RawHeap& heap = GetRawHeap(); |
| const PageBackend* backend = Heap::From(GetHeap())->page_backend(); |
| auto* space = static_cast<LargePageSpace*>( |
| heap.Space(RawHeap::RegularSpaceType::kLarge)); |
| auto* page = LargePage::Create(GetPageBackend(), space, kObjectSize); |
| EXPECT_NE(nullptr, backend->Lookup(page->PayloadStart())); |
| |
| space->AddPage(page); |
| EXPECT_NE(space->end(), std::find(space->begin(), space->end(), page)); |
| |
| space->RemovePage(page); |
| EXPECT_EQ(space->end(), std::find(space->begin(), space->end(), page)); |
| LargePage::Destroy(page); |
| EXPECT_EQ(nullptr, backend->Lookup(page->PayloadStart())); |
| } |
| |
| #if DEBUG |
| TEST_F(PageTest, UnsweptPageDestruction) { |
| RawHeap& heap = GetRawHeap(); |
| { |
| auto* space = static_cast<NormalPageSpace*>( |
| heap.Space(RawHeap::RegularSpaceType::kNormal1)); |
| auto* page = NormalPage::Create(GetPageBackend(), space); |
| space->AddPage(page); |
| EXPECT_DEATH_IF_SUPPORTED(NormalPage::Destroy(page), ""); |
| } |
| { |
| auto* space = static_cast<LargePageSpace*>( |
| heap.Space(RawHeap::RegularSpaceType::kLarge)); |
| auto* page = LargePage::Create(GetPageBackend(), space, |
| 2 * kLargeObjectSizeThreshold); |
| space->AddPage(page); |
| EXPECT_DEATH_IF_SUPPORTED(LargePage::Destroy(page), ""); |
| // Detach page and really destroy page in the parent process so that sweeper |
| // doesn't consider it. |
| space->RemovePage(page); |
| LargePage::Destroy(page); |
| } |
| } |
| #endif |
| |
| TEST_F(PageTest, ObjectHeaderFromInnerAddress) { |
| { |
| auto* object = MakeGarbageCollected<GCed<64>>(GetAllocationHandle()); |
| const HeapObjectHeader& expected = HeapObjectHeader::FromPayload(object); |
| |
| for (auto* inner_ptr = reinterpret_cast<ConstAddress>(object); |
| inner_ptr < reinterpret_cast<ConstAddress>(object + 1); ++inner_ptr) { |
| const HeapObjectHeader& hoh = |
| BasePage::FromPayload(object)->ObjectHeaderFromInnerAddress( |
| inner_ptr); |
| EXPECT_EQ(&expected, &hoh); |
| } |
| } |
| { |
| auto* object = MakeGarbageCollected<GCed<2 * kLargeObjectSizeThreshold>>( |
| GetAllocationHandle()); |
| const HeapObjectHeader& expected = HeapObjectHeader::FromPayload(object); |
| |
| const HeapObjectHeader& hoh = |
| BasePage::FromPayload(object)->ObjectHeaderFromInnerAddress( |
| reinterpret_cast<ConstAddress>(object) + kLargeObjectSizeThreshold); |
| EXPECT_EQ(&expected, &hoh); |
| } |
| } |
| |
| } // namespace internal |
| } // namespace cppgc |