// 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/sweeper.h" #include #include "include/cppgc/allocation.h" #include "include/cppgc/persistent.h" #include "src/heap/cppgc/globals.h" #include "src/heap/cppgc/heap-object-header-inl.h" #include "src/heap/cppgc/heap-object-header.h" #include "src/heap/cppgc/heap-page-inl.h" #include "src/heap/cppgc/heap-visitor.h" #include "src/heap/cppgc/heap.h" #include "src/heap/cppgc/page-memory-inl.h" #include "src/heap/cppgc/page-memory.h" #include "src/heap/cppgc/stats-collector.h" #include "test/unittests/heap/cppgc/tests.h" #include "testing/gtest/include/gtest/gtest.h" namespace cppgc { namespace internal { namespace { size_t g_destructor_callcount; template class GCed : public GarbageCollected> { public: virtual ~GCed() { ++g_destructor_callcount; } virtual void Trace(cppgc::Visitor*) const {} private: char array[Size]; }; class SweeperTest : public testing::TestWithHeap { public: SweeperTest() { g_destructor_callcount = 0; } void Sweep() { Heap* heap = Heap::From(GetHeap()); ResetLinearAllocationBuffers(); Sweeper& sweeper = heap->sweeper(); // Pretend do finish marking as StatsCollector verifies that Notify* // methods are called in the right order. heap->stats_collector()->NotifyMarkingStarted(); heap->stats_collector()->NotifyMarkingCompleted(0); sweeper.Start(Sweeper::Config::kAtomic); sweeper.Finish(); } void MarkObject(void* payload) { HeapObjectHeader& header = HeapObjectHeader::FromPayload(payload); header.TryMarkAtomic(); } PageBackend* GetBackend() { return Heap::From(GetHeap())->page_backend(); } }; } // namespace TEST_F(SweeperTest, SweepUnmarkedNormalObject) { constexpr size_t kObjectSize = 8; using Type = GCed; MakeGarbageCollected(GetAllocationHandle()); EXPECT_EQ(0u, g_destructor_callcount); Sweep(); EXPECT_EQ(1u, g_destructor_callcount); } TEST_F(SweeperTest, DontSweepMarkedNormalObject) { constexpr size_t kObjectSize = 8; using Type = GCed; auto* object = MakeGarbageCollected(GetAllocationHandle()); MarkObject(object); BasePage* page = BasePage::FromPayload(object); BaseSpace* space = page->space(); EXPECT_EQ(0u, g_destructor_callcount); Sweep(); EXPECT_EQ(0u, g_destructor_callcount); // Check that page is returned back to the space. EXPECT_NE(space->end(), std::find(space->begin(), space->end(), page)); EXPECT_NE(nullptr, GetBackend()->Lookup(reinterpret_cast
(object))); } TEST_F(SweeperTest, SweepUnmarkedLargeObject) { constexpr size_t kObjectSize = kLargeObjectSizeThreshold * 2; using Type = GCed; auto* object = MakeGarbageCollected(GetAllocationHandle()); BasePage* page = BasePage::FromPayload(object); BaseSpace* space = page->space(); EXPECT_EQ(0u, g_destructor_callcount); Sweep(); EXPECT_EQ(1u, g_destructor_callcount); // Check that page is gone. EXPECT_EQ(space->end(), std::find(space->begin(), space->end(), page)); EXPECT_EQ(nullptr, GetBackend()->Lookup(reinterpret_cast
(object))); } TEST_F(SweeperTest, DontSweepMarkedLargeObject) { constexpr size_t kObjectSize = kLargeObjectSizeThreshold * 2; using Type = GCed; auto* object = MakeGarbageCollected(GetAllocationHandle()); MarkObject(object); BasePage* page = BasePage::FromPayload(object); BaseSpace* space = page->space(); EXPECT_EQ(0u, g_destructor_callcount); Sweep(); EXPECT_EQ(0u, g_destructor_callcount); // Check that page is returned back to the space. EXPECT_NE(space->end(), std::find(space->begin(), space->end(), page)); EXPECT_NE(nullptr, GetBackend()->Lookup(reinterpret_cast
(object))); } TEST_F(SweeperTest, SweepMultipleObjectsOnPage) { constexpr size_t kObjectSize = 8; using Type = GCed; const size_t kNumberOfObjects = NormalPage::PayloadSize() / (sizeof(Type) + sizeof(HeapObjectHeader)); for (size_t i = 0; i < kNumberOfObjects; ++i) { MakeGarbageCollected(GetAllocationHandle()); } EXPECT_EQ(0u, g_destructor_callcount); Sweep(); EXPECT_EQ(kNumberOfObjects, g_destructor_callcount); } TEST_F(SweeperTest, SweepObjectsOnAllArenas) { MakeGarbageCollected>(GetAllocationHandle()); MakeGarbageCollected>(GetAllocationHandle()); MakeGarbageCollected>(GetAllocationHandle()); MakeGarbageCollected>(GetAllocationHandle()); MakeGarbageCollected>( GetAllocationHandle()); EXPECT_EQ(0u, g_destructor_callcount); Sweep(); EXPECT_EQ(5u, g_destructor_callcount); } TEST_F(SweeperTest, SweepMultiplePagesInSingleSpace) { MakeGarbageCollected>( GetAllocationHandle()); MakeGarbageCollected>( GetAllocationHandle()); MakeGarbageCollected>( GetAllocationHandle()); EXPECT_EQ(0u, g_destructor_callcount); Sweep(); EXPECT_EQ(3u, g_destructor_callcount); } TEST_F(SweeperTest, CoalesceFreeListEntries) { constexpr size_t kObjectSize = 32; using Type = GCed; auto* object1 = MakeGarbageCollected(GetAllocationHandle()); auto* object2 = MakeGarbageCollected(GetAllocationHandle()); auto* object3 = MakeGarbageCollected(GetAllocationHandle()); auto* object4 = MakeGarbageCollected(GetAllocationHandle()); MarkObject(object1); MarkObject(object4); Address object2_start = reinterpret_cast
(&HeapObjectHeader::FromPayload(object2)); Address object3_end = reinterpret_cast
(&HeapObjectHeader::FromPayload(object3)) + HeapObjectHeader::FromPayload(object3).GetSize(); const BasePage* page = BasePage::FromPayload(object2); const FreeList& freelist = NormalPageSpace::From(page->space())->free_list(); const FreeList::Block coalesced_block = {object2_start, object3_end - object2_start}; EXPECT_EQ(0u, g_destructor_callcount); EXPECT_FALSE(freelist.Contains(coalesced_block)); Sweep(); EXPECT_EQ(2u, g_destructor_callcount); EXPECT_TRUE(freelist.Contains(coalesced_block)); } namespace { class GCInDestructor final : public GarbageCollected { public: explicit GCInDestructor(Heap* heap) : heap_(heap) {} ~GCInDestructor() { // Instead of directly calling GC, allocations should be supported here as // well. heap_->CollectGarbage( internal::GarbageCollector::Config::ConservativeAtomicConfig()); } void Trace(Visitor*) const {} private: Heap* heap_; }; } // namespace TEST_F(SweeperTest, SweepDoesNotTriggerRecursiveGC) { auto* internal_heap = internal::Heap::From(GetHeap()); size_t saved_epoch = internal_heap->epoch(); MakeGarbageCollected(GetAllocationHandle(), internal_heap); PreciseGC(); EXPECT_EQ(saved_epoch + 1, internal_heap->epoch()); } TEST_F(SweeperTest, UnmarkObjects) { auto* normal_object = MakeGarbageCollected>(GetAllocationHandle()); auto* large_object = MakeGarbageCollected>( GetAllocationHandle()); auto& normal_object_header = HeapObjectHeader::FromPayload(normal_object); auto& large_object_header = HeapObjectHeader::FromPayload(large_object); normal_object_header.TryMarkAtomic(); large_object_header.TryMarkAtomic(); EXPECT_TRUE(normal_object_header.IsMarked()); EXPECT_TRUE(large_object_header.IsMarked()); Sweep(); #if !defined(CPPGC_YOUNG_GENERATION) EXPECT_FALSE(normal_object_header.IsMarked()); EXPECT_FALSE(large_object_header.IsMarked()); #else EXPECT_TRUE(normal_object_header.IsMarked()); EXPECT_TRUE(large_object_header.IsMarked()); #endif } } // namespace internal } // namespace cppgc