// 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. #if defined(CPPGC_YOUNG_GENERATION) #include <initializer_list> #include <vector> #include "include/cppgc/allocation.h" #include "include/cppgc/explicit-management.h" #include "include/cppgc/heap-consistency.h" #include "include/cppgc/internal/caged-heap-local-data.h" #include "include/cppgc/persistent.h" #include "src/heap/cppgc/heap-object-header.h" #include "src/heap/cppgc/heap-visitor.h" #include "src/heap/cppgc/heap.h" #include "test/unittests/heap/cppgc/tests.h" #include "testing/gtest/include/gtest/gtest.h" namespace cppgc { namespace internal { namespace { bool IsHeapObjectYoung(void* obj) { return HeapObjectHeader::FromObject(obj).IsYoung(); } bool IsHeapObjectOld(void* obj) { return !IsHeapObjectYoung(obj); } class SimpleGCedBase : public GarbageCollected<SimpleGCedBase> { public: static size_t destructed_objects; virtual ~SimpleGCedBase() { ++destructed_objects; } virtual void Trace(Visitor* v) const { v->Trace(next); } Member<SimpleGCedBase> next; }; size_t SimpleGCedBase::destructed_objects; template <size_t Size> class SimpleGCed : public SimpleGCedBase { char array[Size]; }; using Small = SimpleGCed<64>; using Large = SimpleGCed<kLargeObjectSizeThreshold * 2>; template <typename Type> struct OtherType; template <> struct OtherType<Small> { using Type = Large; }; template <> struct OtherType<Large> { using Type = Small; }; void ExpectPageYoung(BasePage& page) { EXPECT_TRUE(page.contains_young_objects()); auto& age_table = CagedHeapLocalData::Get().age_table; EXPECT_EQ(AgeTable::Age::kYoung, age_table.GetAgeForRange( CagedHeap::OffsetFromAddress(page.PayloadStart()), CagedHeap::OffsetFromAddress(page.PayloadEnd()))); } void ExpectPageMixed(BasePage& page) { EXPECT_TRUE(page.contains_young_objects()); auto& age_table = CagedHeapLocalData::Get().age_table; EXPECT_EQ(AgeTable::Age::kMixed, age_table.GetAgeForRange( CagedHeap::OffsetFromAddress(page.PayloadStart()), CagedHeap::OffsetFromAddress(page.PayloadEnd()))); } void ExpectPageOld(BasePage& page) { EXPECT_FALSE(page.contains_young_objects()); auto& age_table = CagedHeapLocalData::Get().age_table; EXPECT_EQ(AgeTable::Age::kOld, age_table.GetAgeForRange( CagedHeap::OffsetFromAddress(page.PayloadStart()), CagedHeap::OffsetFromAddress(page.PayloadEnd()))); } class RememberedSetExtractor : HeapVisitor<RememberedSetExtractor> { friend class HeapVisitor<RememberedSetExtractor>; public: static std::set<void*> Extract(cppgc::Heap* heap) { RememberedSetExtractor extractor; extractor.Traverse(Heap::From(heap)->raw_heap()); return std::move(extractor.slots_); } private: void VisitPage(BasePage& page) { auto* slot_set = page.slot_set(); if (!slot_set) return; const uintptr_t page_start = reinterpret_cast<uintptr_t>(&page); const size_t buckets_size = SlotSet::BucketsForSize(page.AllocatedSize()); slot_set->Iterate( page_start, 0, buckets_size, [this](SlotSet::Address slot) { slots_.insert(reinterpret_cast<void*>(slot)); return heap::base::KEEP_SLOT; }, SlotSet::EmptyBucketMode::FREE_EMPTY_BUCKETS); } bool VisitNormalPage(NormalPage& page) { VisitPage(page); return true; } bool VisitLargePage(LargePage& page) { VisitPage(page); return true; } std::set<void*> slots_; }; } // namespace class MinorGCTest : public testing::TestWithHeap { public: MinorGCTest() : testing::TestWithHeap() { // Enable young generation flag and run GC. After the first run the heap // will enable minor GC. Heap::From(GetHeap())->EnableGenerationalGC(); CollectMajor(); SimpleGCedBase::destructed_objects = 0; } ~MinorGCTest() override { Heap::From(GetHeap())->Terminate(); } static size_t DestructedObjects() { return SimpleGCedBase::destructed_objects; } void CollectMinor() { Heap::From(GetHeap())->CollectGarbage(GCConfig::MinorPreciseAtomicConfig()); } void CollectMinorWithStack() { Heap::From(GetHeap())->CollectGarbage( GCConfig::MinorConservativeAtomicConfig()); } void CollectMajor() { Heap::From(GetHeap())->CollectGarbage(GCConfig::PreciseAtomicConfig()); } void CollectMajorWithStack() { Heap::From(GetHeap())->CollectGarbage(GCConfig::ConservativeAtomicConfig()); } const auto& RememberedSourceObjects() const { return Heap::From(GetHeap())->remembered_set().remembered_source_objects_; } const auto& RememberedInConstructionObjects() const { return Heap::From(GetHeap()) ->remembered_set() .remembered_in_construction_objects_.previous; } }; template <typename SmallOrLarge> class MinorGCTestForType : public MinorGCTest { public: using Type = SmallOrLarge; }; using ObjectTypes = ::testing::Types<Small, Large>; TYPED_TEST_SUITE(MinorGCTestForType, ObjectTypes); namespace { enum class GCType { kMinor, kMajor, }; enum class StackType { kWithout, kWith, }; template <GCType gc_type, StackType stack_type, typename... Args> void RunGCAndExpectObjectsPromoted(MinorGCTest& test, Args*... args) { EXPECT_TRUE((IsHeapObjectYoung(args) && ...)); if constexpr (gc_type == GCType::kMajor) { if constexpr (stack_type == StackType::kWithout) { test.CollectMajor(); } else { test.CollectMajorWithStack(); } } else { if constexpr (stack_type == StackType::kWithout) { test.CollectMinor(); } else { test.CollectMinorWithStack(); } } EXPECT_TRUE((IsHeapObjectOld(args) && ...)); } struct ExpectRememberedSlotsAdded final { ExpectRememberedSlotsAdded( const MinorGCTest& test, std::initializer_list<void*> slots_expected_to_be_remembered) : test_(test), slots_expected_to_be_remembered_(slots_expected_to_be_remembered), initial_slots_(RememberedSetExtractor::Extract(test.GetHeap())) { // Check that the remembered set doesn't contain specified slots. EXPECT_FALSE(std::includes(initial_slots_.begin(), initial_slots_.end(), slots_expected_to_be_remembered_.begin(), slots_expected_to_be_remembered_.end())); } ~ExpectRememberedSlotsAdded() { const auto current_slots = RememberedSetExtractor::Extract(test_.GetHeap()); EXPECT_EQ(initial_slots_.size() + slots_expected_to_be_remembered_.size(), current_slots.size()); EXPECT_TRUE(std::includes(current_slots.begin(), current_slots.end(), slots_expected_to_be_remembered_.begin(), slots_expected_to_be_remembered_.end())); } private: const MinorGCTest& test_; std::set<void*> slots_expected_to_be_remembered_; std::set<void*> initial_slots_; }; struct ExpectRememberedSlotsRemoved final { ExpectRememberedSlotsRemoved( const MinorGCTest& test, std::initializer_list<void*> slots_expected_to_be_removed) : test_(test), slots_expected_to_be_removed_(slots_expected_to_be_removed), initial_slots_(RememberedSetExtractor::Extract(test.GetHeap())) { DCHECK_GE(initial_slots_.size(), slots_expected_to_be_removed_.size()); // Check that the remembered set does contain specified slots to be removed. EXPECT_TRUE(std::includes(initial_slots_.begin(), initial_slots_.end(), slots_expected_to_be_removed_.begin(), slots_expected_to_be_removed_.end())); } ~ExpectRememberedSlotsRemoved() { const auto current_slots = RememberedSetExtractor::Extract(test_.GetHeap()); EXPECT_EQ(initial_slots_.size() - slots_expected_to_be_removed_.size(), current_slots.size()); EXPECT_FALSE(std::includes(current_slots.begin(), current_slots.end(), slots_expected_to_be_removed_.begin(), slots_expected_to_be_removed_.end())); } private: const MinorGCTest& test_; std::set<void*> slots_expected_to_be_removed_; std::set<void*> initial_slots_; }; struct ExpectNoRememberedSlotsAdded final { explicit ExpectNoRememberedSlotsAdded(const MinorGCTest& test) : test_(test), initial_remembered_slots_( RememberedSetExtractor::Extract(test.GetHeap())) {} ~ExpectNoRememberedSlotsAdded() { EXPECT_EQ(initial_remembered_slots_, RememberedSetExtractor::Extract(test_.GetHeap())); } private: const MinorGCTest& test_; std::set<void*> initial_remembered_slots_; }; } // namespace TYPED_TEST(MinorGCTestForType, MinorCollection) { using Type = typename TestFixture::Type; MakeGarbageCollected<Type>(this->GetAllocationHandle()); EXPECT_EQ(0u, TestFixture::DestructedObjects()); MinorGCTest::CollectMinor(); EXPECT_EQ(1u, TestFixture::DestructedObjects()); { subtle::NoGarbageCollectionScope no_gc_scope(*Heap::From(this->GetHeap())); Type* prev = nullptr; for (size_t i = 0; i < 64; ++i) { auto* ptr = MakeGarbageCollected<Type>(this->GetAllocationHandle()); ptr->next = prev; prev = ptr; } } MinorGCTest::CollectMinor(); EXPECT_EQ(65u, TestFixture::DestructedObjects()); } TYPED_TEST(MinorGCTestForType, StickyBits) { using Type = typename TestFixture::Type; Persistent<Type> p1 = MakeGarbageCollected<Type>(this->GetAllocationHandle()); TestFixture::CollectMinor(); EXPECT_FALSE(HeapObjectHeader::FromObject(p1.Get()).IsYoung()); TestFixture::CollectMajor(); EXPECT_FALSE(HeapObjectHeader::FromObject(p1.Get()).IsYoung()); EXPECT_EQ(0u, TestFixture::DestructedObjects()); } TYPED_TEST(MinorGCTestForType, OldObjectIsNotVisited) { using Type = typename TestFixture::Type; Persistent<Type> p = MakeGarbageCollected<Type>(this->GetAllocationHandle()); TestFixture::CollectMinor(); EXPECT_EQ(0u, TestFixture::DestructedObjects()); EXPECT_FALSE(HeapObjectHeader::FromObject(p.Get()).IsYoung()); // Check that the old deleted object won't be visited during minor GC. Type* raw = p.Release(); TestFixture::CollectMinor(); EXPECT_EQ(0u, TestFixture::DestructedObjects()); EXPECT_FALSE(HeapObjectHeader::FromObject(raw).IsYoung()); EXPECT_FALSE(HeapObjectHeader::FromObject(raw).IsFree()); // Check that the old deleted object will be revisited in major GC. TestFixture::CollectMajor(); EXPECT_EQ(1u, TestFixture::DestructedObjects()); } template <typename Type1, typename Type2> void InterGenerationalPointerTest(MinorGCTest* test, cppgc::Heap* heap) { Persistent<Type1> old = MakeGarbageCollected<Type1>(heap->GetAllocationHandle()); test->CollectMinor(); EXPECT_FALSE(HeapObjectHeader::FromObject(old.Get()).IsYoung()); Type2* young = nullptr; { subtle::NoGarbageCollectionScope no_gc_scope(*Heap::From(heap)); // Allocate young objects. for (size_t i = 0; i < 64; ++i) { auto* ptr = MakeGarbageCollected<Type2>(heap->GetAllocationHandle()); ptr->next = young; young = ptr; EXPECT_TRUE(HeapObjectHeader::FromObject(young).IsYoung()); const uintptr_t offset = CagedHeap::OffsetFromAddress(young); // Age may be young or unknown. EXPECT_NE(AgeTable::Age::kOld, CagedHeapLocalData::Get().age_table.GetAge(offset)); } } auto remembered_set_size_before_barrier = RememberedSetExtractor::Extract(test->GetHeap()).size(); // Issue generational barrier. old->next = young; auto remembered_set_size_after_barrier = RememberedSetExtractor::Extract(test->GetHeap()).size(); EXPECT_EQ(remembered_set_size_before_barrier + 1u, remembered_set_size_after_barrier); // Check that the remembered set is visited. test->CollectMinor(); EXPECT_EQ(0u, MinorGCTest::DestructedObjects()); EXPECT_TRUE(RememberedSetExtractor::Extract(test->GetHeap()).empty()); for (size_t i = 0; i < 64; ++i) { EXPECT_FALSE(HeapObjectHeader::FromObject(young).IsFree()); EXPECT_FALSE(HeapObjectHeader::FromObject(young).IsYoung()); young = static_cast<Type2*>(young->next.Get()); } old.Release(); test->CollectMajor(); EXPECT_EQ(65u, MinorGCTest::DestructedObjects()); } TYPED_TEST(MinorGCTestForType, InterGenerationalPointerForSamePageTypes) { using Type = typename TestFixture::Type; InterGenerationalPointerTest<Type, Type>(this, this->GetHeap()); } TYPED_TEST(MinorGCTestForType, InterGenerationalPointerForDifferentPageTypes) { using Type = typename TestFixture::Type; InterGenerationalPointerTest<Type, typename OtherType<Type>::Type>( this, this->GetHeap()); } TYPED_TEST(MinorGCTestForType, OmitGenerationalBarrierForOnStackObject) { using Type = typename TestFixture::Type; class StackAllocated : GarbageCollected<StackAllocated> { CPPGC_STACK_ALLOCATED(); public: Type* ptr = nullptr; } stack_object; // Try issuing generational barrier for on-stack object. stack_object.ptr = MakeGarbageCollected<Type>(this->GetAllocationHandle()); subtle::HeapConsistency::WriteBarrierParams params; EXPECT_EQ(subtle::HeapConsistency::WriteBarrierType::kNone, subtle::HeapConsistency::GetWriteBarrierType( reinterpret_cast<void*>(&stack_object.ptr), stack_object.ptr, params)); } TYPED_TEST(MinorGCTestForType, OmitGenerationalBarrierForSentinels) { using Type = typename TestFixture::Type; Persistent<Type> old = MakeGarbageCollected<Type>(this->GetAllocationHandle()); TestFixture::CollectMinor(); EXPECT_FALSE(HeapObjectHeader::FromObject(old.Get()).IsYoung()); { ExpectNoRememberedSlotsAdded _(*this); // Try issuing generational barrier for nullptr. old->next = static_cast<Type*>(nullptr); } { ExpectNoRememberedSlotsAdded _(*this); // Try issuing generational barrier for sentinel. old->next = kSentinelPointer; } } template <typename From, typename To> void TestRememberedSetInvalidation(MinorGCTest& test) { Persistent<From> old = MakeGarbageCollected<From>(test.GetAllocationHandle()); test.CollectMinor(); auto* young = MakeGarbageCollected<To>(test.GetAllocationHandle()); { ExpectRememberedSlotsAdded _(test, {old->next.GetSlotForTesting()}); // Issue the generational barrier. old->next = young; } { ExpectRememberedSlotsRemoved _(test, {old->next.GetSlotForTesting()}); // Release the persistent and free the old object. auto* old_raw = old.Release(); subtle::FreeUnreferencedObject(test.GetHeapHandle(), *old_raw); } // Visiting remembered slots must not fail. test.CollectMinor(); } TYPED_TEST(MinorGCTestForType, RememberedSetInvalidationOnPromptlyFree) { using Type1 = typename TestFixture::Type; using Type2 = typename OtherType<Type1>::Type; TestRememberedSetInvalidation<Type1, Type1>(*this); TestRememberedSetInvalidation<Type1, Type2>(*this); } TEST_F(MinorGCTest, RememberedSetInvalidationOnShrink) { using Member = Member<Small>; static constexpr size_t kTrailingMembers = 64; static constexpr size_t kBytesToAllocate = kTrailingMembers * sizeof(Member); static constexpr size_t kFirstMemberToInvalidate = kTrailingMembers / 2; static constexpr size_t kLastMemberToInvalidate = kTrailingMembers; // Create an object with additional kBytesToAllocate bytes. Persistent<Small> old = MakeGarbageCollected<Small>( this->GetAllocationHandle(), AdditionalBytes(kBytesToAllocate)); auto get_member = [&old](size_t i) -> Member& { return *reinterpret_cast<Member*>(reinterpret_cast<uint8_t*>(old.Get()) + sizeof(Small) + i * sizeof(Member)); }; CollectMinor(); auto* young = MakeGarbageCollected<Small>(GetAllocationHandle()); const size_t remembered_set_size_before_barrier = RememberedSetExtractor::Extract(GetHeap()).size(); // Issue the generational barriers. for (size_t i = kFirstMemberToInvalidate; i < kLastMemberToInvalidate; ++i) { // Construct the member. new (&get_member(i)) Member; // Issue the barrier. get_member(i) = young; } const auto remembered_set_size_after_barrier = RememberedSetExtractor::Extract(GetHeap()).size(); // Check that barriers hit (kLastMemberToInvalidate - // kFirstMemberToInvalidate) times. EXPECT_EQ(remembered_set_size_before_barrier + (kLastMemberToInvalidate - kFirstMemberToInvalidate), remembered_set_size_after_barrier); // Shrink the buffer for old object. subtle::Resize(*old, AdditionalBytes(kBytesToAllocate / 2)); const auto remembered_set_after_shrink = RememberedSetExtractor::Extract(GetHeap()).size(); // Check that the reference was invalidated. EXPECT_EQ(remembered_set_size_before_barrier, remembered_set_after_shrink); // Visiting remembered slots must not fail. CollectMinor(); } namespace { template <typename Value> struct InlinedObject { struct Inner { Inner() = default; explicit Inner(AllocationHandle& handle) : ref(MakeGarbageCollected<Value>(handle)) {} void Trace(Visitor* v) const { v->Trace(ref); } double d = -1.; Member<Value> ref; }; InlinedObject() = default; explicit InlinedObject(AllocationHandle& handle) : ref(MakeGarbageCollected<Value>(handle)), inner(handle) {} void Trace(cppgc::Visitor* v) const { v->Trace(ref); v->Trace(inner); } int a_ = -1; Member<Value> ref; Inner inner; }; template <typename Value> class GCedWithInlinedArray : public GarbageCollected<GCedWithInlinedArray<Value>> { public: static constexpr size_t kNumObjects = 16; GCedWithInlinedArray(HeapHandle& heap_handle, AllocationHandle& alloc_handle) : heap_handle_(heap_handle), alloc_handle_(alloc_handle) {} using WriteBarrierParams = subtle::HeapConsistency::WriteBarrierParams; using HeapConsistency = subtle::HeapConsistency; void SetInPlaceRange(size_t from, size_t to) { DCHECK_GT(to, from); DCHECK_GT(kNumObjects, from); for (; from != to; ++from) new (&objects[from]) InlinedObject<Value>(alloc_handle_); GenerationalBarrierForSourceObject(&objects[from]); } void Trace(cppgc::Visitor* v) const { for (const auto& object : objects) v->Trace(object); } InlinedObject<Value> objects[kNumObjects]; private: void GenerationalBarrierForSourceObject(void* object) { DCHECK(object); WriteBarrierParams params; const auto barrier_type = HeapConsistency::GetWriteBarrierType( object, params, [this]() -> HeapHandle& { return heap_handle_; }); EXPECT_EQ(HeapConsistency::WriteBarrierType::kGenerational, barrier_type); HeapConsistency::GenerationalBarrierForSourceObject(params, object); } HeapHandle& heap_handle_; AllocationHandle& alloc_handle_; }; } // namespace TYPED_TEST(MinorGCTestForType, GenerationalBarrierDeferredTracing) { using Type = typename TestFixture::Type; Persistent<GCedWithInlinedArray<Type>> array = MakeGarbageCollected<GCedWithInlinedArray<Type>>( this->GetAllocationHandle(), this->GetHeapHandle(), this->GetAllocationHandle()); this->CollectMinor(); EXPECT_TRUE(IsHeapObjectOld(array.Get())); const auto& remembered_objects = this->RememberedSourceObjects(); { ExpectNoRememberedSlotsAdded _(*this); EXPECT_EQ(0u, remembered_objects.count( &HeapObjectHeader::FromObject(array->objects))); array->SetInPlaceRange(2, 4); EXPECT_EQ(1u, remembered_objects.count( &HeapObjectHeader::FromObject(array->objects))); } RunGCAndExpectObjectsPromoted<GCType::kMinor, StackType::kWithout>( *this, array->objects[2].ref.Get(), array->objects[2].inner.ref.Get(), array->objects[3].ref.Get(), array->objects[3].inner.ref.Get()); EXPECT_EQ(0u, remembered_objects.size()); } namespace { class GCedWithCustomWeakCallback final : public GarbageCollected<GCedWithCustomWeakCallback> { public: static size_t custom_callback_called; void CustomWeakCallbackMethod(const LivenessBroker& broker) { custom_callback_called++; } void Trace(cppgc::Visitor* visitor) const { visitor->RegisterWeakCallbackMethod< GCedWithCustomWeakCallback, &GCedWithCustomWeakCallback::CustomWeakCallbackMethod>(this); } }; size_t GCedWithCustomWeakCallback::custom_callback_called = 0; } // namespace TEST_F(MinorGCTest, ReexecuteCustomCallback) { // Create an object with additional kBytesToAllocate bytes. Persistent<GCedWithCustomWeakCallback> old = MakeGarbageCollected<GCedWithCustomWeakCallback>(GetAllocationHandle()); CollectMinor(); EXPECT_EQ(1u, GCedWithCustomWeakCallback::custom_callback_called); CollectMinor(); EXPECT_EQ(2u, GCedWithCustomWeakCallback::custom_callback_called); CollectMinor(); EXPECT_EQ(3u, GCedWithCustomWeakCallback::custom_callback_called); CollectMajor(); // The callback must be called only once. EXPECT_EQ(4u, GCedWithCustomWeakCallback::custom_callback_called); } TEST_F(MinorGCTest, AgeTableIsReset) { using Type1 = SimpleGCed<16>; using Type2 = SimpleGCed<64>; using Type3 = SimpleGCed<kLargeObjectSizeThreshold * 2>; Persistent<Type1> p1 = MakeGarbageCollected<Type1>(GetAllocationHandle()); Persistent<Type2> p2 = MakeGarbageCollected<Type2>(GetAllocationHandle()); Persistent<Type3> p3 = MakeGarbageCollected<Type3>(GetAllocationHandle()); auto* page1 = BasePage::FromPayload(p1.Get()); auto* page2 = BasePage::FromPayload(p2.Get()); auto* page3 = BasePage::FromPayload(p3.Get()); ASSERT_FALSE(page1->is_large()); ASSERT_FALSE(page2->is_large()); ASSERT_TRUE(page3->is_large()); ASSERT_NE(page1, page2); ASSERT_NE(page1, page3); ASSERT_NE(page2, page3); // First, expect all the pages to be young. ExpectPageYoung(*page1); ExpectPageYoung(*page2); ExpectPageYoung(*page3); CollectMinor(); // Expect pages to be promoted after the minor GC. ExpectPageOld(*page1); ExpectPageOld(*page2); ExpectPageOld(*page3); // Allocate another objects on the normal pages and a new large page. p1 = MakeGarbageCollected<Type1>(GetAllocationHandle()); p2 = MakeGarbageCollected<Type2>(GetAllocationHandle()); p3 = MakeGarbageCollected<Type3>(GetAllocationHandle()); // Expect now the normal pages to be mixed. ExpectPageMixed(*page1); ExpectPageMixed(*page2); // The large page must remain old. ExpectPageOld(*page3); CollectMajor(); // After major GC all the pages must also become old. ExpectPageOld(*page1); ExpectPageOld(*page2); ExpectPageOld(*BasePage::FromPayload(p3.Get())); } namespace { template <GCType type> struct GCOnConstruction { explicit GCOnConstruction(MinorGCTest& test, size_t depth) { if constexpr (type == GCType::kMajor) { test.CollectMajorWithStack(); } else { test.CollectMinorWithStack(); } EXPECT_EQ(depth, test.RememberedInConstructionObjects().size()); } }; template <GCType type> struct InConstructionWithYoungRef : GarbageCollected<InConstructionWithYoungRef<type>> { using ValueType = SimpleGCed<64>; explicit InConstructionWithYoungRef(MinorGCTest& test) : call_gc(test, 1u), m(MakeGarbageCollected<ValueType>(test.GetAllocationHandle())) {} void Trace(Visitor* v) const { v->Trace(m); } GCOnConstruction<type> call_gc; Member<ValueType> m; }; } // namespace TEST_F(MinorGCTest, RevisitInConstructionObjectsMinorMinorWithStack) { static constexpr auto kFirstGCType = GCType::kMinor; auto* gced = MakeGarbageCollected<InConstructionWithYoungRef<kFirstGCType>>( GetAllocationHandle(), *this); RunGCAndExpectObjectsPromoted<GCType::kMinor, StackType::kWith>( *this, gced->m.Get()); EXPECT_EQ(0u, RememberedInConstructionObjects().size()); } TEST_F(MinorGCTest, RevisitInConstructionObjectsMinorMinorWithoutStack) { static constexpr auto kFirstGCType = GCType::kMinor; Persistent<InConstructionWithYoungRef<kFirstGCType>> gced = MakeGarbageCollected<InConstructionWithYoungRef<kFirstGCType>>( GetAllocationHandle(), *this); RunGCAndExpectObjectsPromoted<GCType::kMinor, StackType::kWithout>( *this, gced->m.Get()); EXPECT_EQ(0u, RememberedInConstructionObjects().size()); } TEST_F(MinorGCTest, RevisitInConstructionObjectsMajorMinorWithStack) { static constexpr auto kFirstGCType = GCType::kMajor; auto* gced = MakeGarbageCollected<InConstructionWithYoungRef<kFirstGCType>>( GetAllocationHandle(), *this); RunGCAndExpectObjectsPromoted<GCType::kMinor, StackType::kWith>( *this, gced->m.Get()); EXPECT_EQ(0u, RememberedInConstructionObjects().size()); } TEST_F(MinorGCTest, RevisitInConstructionObjectsMajorMinorWithoutStack) { static constexpr auto kFirstGCType = GCType::kMajor; Persistent<InConstructionWithYoungRef<kFirstGCType>> gced = MakeGarbageCollected<InConstructionWithYoungRef<kFirstGCType>>( GetAllocationHandle(), *this); RunGCAndExpectObjectsPromoted<GCType::kMinor, StackType::kWithout>( *this, gced->m.Get()); EXPECT_EQ(0u, RememberedInConstructionObjects().size()); } TEST_F(MinorGCTest, PreviousInConstructionObjectsAreDroppedAfterFullGC) { MakeGarbageCollected<InConstructionWithYoungRef<GCType::kMinor>>( GetAllocationHandle(), *this); EXPECT_EQ(1u, RememberedInConstructionObjects().size()); CollectMajor(); EXPECT_EQ(0u, RememberedInConstructionObjects().size()); } namespace { template <GCType type> struct NestedInConstructionWithYoungRef : GarbageCollected<NestedInConstructionWithYoungRef<type>> { using ValueType = SimpleGCed<64>; NestedInConstructionWithYoungRef(MinorGCTest& test, size_t depth) : NestedInConstructionWithYoungRef(test, 1, depth) {} NestedInConstructionWithYoungRef(MinorGCTest& test, size_t current_depth, size_t max_depth) : current_depth(current_depth), max_depth(max_depth), next(current_depth != max_depth ? MakeGarbageCollected<NestedInConstructionWithYoungRef<type>>( test.GetAllocationHandle(), test, current_depth + 1, max_depth) : nullptr), call_gc(test, current_depth), m(MakeGarbageCollected<ValueType>(test.GetAllocationHandle())) {} void Trace(Visitor* v) const { v->Trace(next); v->Trace(m); } size_t current_depth = 0; size_t max_depth = 0; Member<NestedInConstructionWithYoungRef<type>> next; GCOnConstruction<type> call_gc; Member<ValueType> m; }; } // namespace TEST_F(MinorGCTest, RevisitNestedInConstructionObjects) { static constexpr auto kFirstGCType = GCType::kMinor; Persistent<NestedInConstructionWithYoungRef<kFirstGCType>> gced = MakeGarbageCollected<NestedInConstructionWithYoungRef<kFirstGCType>>( GetAllocationHandle(), *this, 10); CollectMinor(); for (auto* p = gced.Get(); p; p = p->next.Get()) { EXPECT_TRUE(IsHeapObjectOld(p)); EXPECT_TRUE(IsHeapObjectOld(p->m)); } EXPECT_EQ(0u, RememberedInConstructionObjects().size()); } } // namespace internal } // namespace cppgc #endif