| // 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 "include/cppgc/allocation.h" |
| #include "include/cppgc/persistent.h" |
| #include "src/heap/cppgc/heap-object-header.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 { |
| |
| 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 final : 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; |
| }; |
| |
| class MinorGCTest : public testing::TestWithHeap { |
| public: |
| MinorGCTest() { |
| CollectMajor(); |
| SimpleGCedBase::destructed_objects = 0; |
| } |
| |
| static size_t DestructedObjects() { |
| return SimpleGCedBase::destructed_objects; |
| } |
| |
| void CollectMinor() { |
| Heap::From(GetHeap())->CollectGarbage( |
| Heap::Config::MinorPreciseAtomicConfig()); |
| } |
| void CollectMajor() { |
| Heap::From(GetHeap())->CollectGarbage(Heap::Config::PreciseAtomicConfig()); |
| } |
| }; |
| |
| template <typename SmallOrLarge> |
| class MinorGCTestForType : public MinorGCTest { |
| public: |
| using Type = SmallOrLarge; |
| }; |
| |
| } // namespace |
| |
| using ObjectTypes = ::testing::Types<Small, Large>; |
| TYPED_TEST_SUITE(MinorGCTestForType, ObjectTypes); |
| |
| 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()); |
| |
| { |
| Heap::NoGCScope 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::FromPayload(p1.Get()).IsYoung()); |
| TestFixture::CollectMajor(); |
| EXPECT_FALSE(HeapObjectHeader::FromPayload(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::FromPayload(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::FromPayload(raw).IsYoung()); |
| EXPECT_FALSE(HeapObjectHeader::FromPayload(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::FromPayload(old.Get()).IsYoung()); |
| |
| Type2* young = nullptr; |
| |
| { |
| Heap::NoGCScope 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::FromPayload(young).IsYoung()); |
| } |
| } |
| |
| const auto& set = Heap::From(heap)->remembered_slots(); |
| auto set_size_before = set.size(); |
| |
| // Issue generational barrier. |
| old->next = young; |
| |
| EXPECT_EQ(set_size_before + 1u, set.size()); |
| |
| // Check that the remembered set is visited. |
| test->CollectMinor(); |
| |
| EXPECT_EQ(0u, MinorGCTest::DestructedObjects()); |
| EXPECT_TRUE(set.empty()); |
| |
| for (size_t i = 0; i < 64; ++i) { |
| EXPECT_FALSE(HeapObjectHeader::FromPayload(young).IsFree()); |
| EXPECT_FALSE(HeapObjectHeader::FromPayload(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; |
| |
| auto* new_object = MakeGarbageCollected<Type>(this->GetAllocationHandle()); |
| |
| const auto& set = Heap::From(this->GetHeap())->remembered_slots(); |
| const size_t set_size_before_barrier = set.size(); |
| |
| // Try issuing generational barrier for on-stack object. |
| stack_object.ptr = new_object; |
| WriteBarrier::MarkingBarrier(reinterpret_cast<void*>(&stack_object.ptr), |
| new_object); |
| |
| EXPECT_EQ(set_size_before_barrier, set.size()); |
| } |
| |
| TYPED_TEST(MinorGCTestForType, OmitGenerationalBarrierForSentinels) { |
| using Type = typename TestFixture::Type; |
| |
| Persistent<Type> old = |
| MakeGarbageCollected<Type>(this->GetAllocationHandle()); |
| |
| TestFixture::CollectMinor(); |
| EXPECT_FALSE(HeapObjectHeader::FromPayload(old.Get()).IsYoung()); |
| |
| const auto& set = Heap::From(this->GetHeap())->remembered_slots(); |
| const size_t set_size_before_barrier = set.size(); |
| |
| // Try issuing generational barrier for nullptr. |
| old->next = static_cast<Type*>(nullptr); |
| EXPECT_EQ(set_size_before_barrier, set.size()); |
| |
| // Try issuing generational barrier for sentinel. |
| old->next = static_cast<Type*>(kSentinelPointer); |
| EXPECT_EQ(set_size_before_barrier, set.size()); |
| } |
| } // namespace internal |
| } // namespace cppgc |
| |
| #endif |