// 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/marker.h" #include "include/cppgc/allocation.h" #include "include/cppgc/internal/pointer-policies.h" #include "include/cppgc/member.h" #include "include/cppgc/persistent.h" #include "src/heap/cppgc/heap-object-header.h" #include "src/heap/cppgc/marking-visitor.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 { class MarkerTest : public testing::TestWithHeap { public: using MarkingConfig = Marker::MarkingConfig; void DoMarking(MarkingConfig::StackState stack_state) { const MarkingConfig config = {MarkingConfig::CollectionType::kMajor, stack_state}; auto* heap = Heap::From(GetHeap()); InitializeMarker(*heap, GetPlatformHandle().get(), config); marker_->FinishMarking(stack_state); marker_->ProcessWeakness(); // Pretend do finish sweeping as StatsCollector verifies that Notify* // methods are called in the right order. heap->stats_collector()->NotifySweepingCompleted(); } void InitializeMarker(HeapBase& heap, cppgc::Platform* platform, MarkingConfig config) { marker_ = MarkerFactory::CreateAndStartMarking(heap, platform, config); } Marker* marker() const { return marker_.get(); } private: std::unique_ptr marker_; }; class GCed : public GarbageCollected { public: void SetChild(GCed* child) { child_ = child; } void SetWeakChild(GCed* child) { weak_child_ = child; } GCed* child() const { return child_.Get(); } GCed* weak_child() const { return weak_child_.Get(); } void Trace(cppgc::Visitor* visitor) const { visitor->Trace(child_); visitor->Trace(weak_child_); } private: Member child_; WeakMember weak_child_; }; template V8_NOINLINE T access(volatile const T& t) { return t; } } // namespace TEST_F(MarkerTest, PersistentIsMarked) { Persistent object = MakeGarbageCollected(GetAllocationHandle()); HeapObjectHeader& header = HeapObjectHeader::FromPayload(object); EXPECT_FALSE(header.IsMarked()); DoMarking(MarkingConfig::StackState::kNoHeapPointers); EXPECT_TRUE(header.IsMarked()); } TEST_F(MarkerTest, ReachableMemberIsMarked) { Persistent parent = MakeGarbageCollected(GetAllocationHandle()); parent->SetChild(MakeGarbageCollected(GetAllocationHandle())); HeapObjectHeader& header = HeapObjectHeader::FromPayload(parent->child()); EXPECT_FALSE(header.IsMarked()); DoMarking(MarkingConfig::StackState::kNoHeapPointers); EXPECT_TRUE(header.IsMarked()); } TEST_F(MarkerTest, UnreachableMemberIsNotMarked) { Member object = MakeGarbageCollected(GetAllocationHandle()); HeapObjectHeader& header = HeapObjectHeader::FromPayload(object); EXPECT_FALSE(header.IsMarked()); DoMarking(MarkingConfig::StackState::kNoHeapPointers); EXPECT_FALSE(header.IsMarked()); } TEST_F(MarkerTest, ObjectReachableFromStackIsMarked) { GCed* object = MakeGarbageCollected(GetAllocationHandle()); EXPECT_FALSE(HeapObjectHeader::FromPayload(object).IsMarked()); DoMarking(MarkingConfig::StackState::kMayContainHeapPointers); EXPECT_TRUE(HeapObjectHeader::FromPayload(object).IsMarked()); access(object); } TEST_F(MarkerTest, ObjectReachableOnlyFromStackIsNotMarkedIfStackIsEmpty) { GCed* object = MakeGarbageCollected(GetAllocationHandle()); HeapObjectHeader& header = HeapObjectHeader::FromPayload(object); EXPECT_FALSE(header.IsMarked()); DoMarking(MarkingConfig::StackState::kNoHeapPointers); EXPECT_FALSE(header.IsMarked()); access(object); } TEST_F(MarkerTest, WeakReferenceToUnreachableObjectIsCleared) { { WeakPersistent weak_object = MakeGarbageCollected(GetAllocationHandle()); EXPECT_TRUE(weak_object); DoMarking(MarkingConfig::StackState::kNoHeapPointers); EXPECT_FALSE(weak_object); } { Persistent parent = MakeGarbageCollected(GetAllocationHandle()); parent->SetWeakChild(MakeGarbageCollected(GetAllocationHandle())); EXPECT_TRUE(parent->weak_child()); DoMarking(MarkingConfig::StackState::kNoHeapPointers); EXPECT_FALSE(parent->weak_child()); } } TEST_F(MarkerTest, WeakReferenceToReachableObjectIsNotCleared) { // Reachable from Persistent { Persistent object = MakeGarbageCollected(GetAllocationHandle()); WeakPersistent weak_object(object); EXPECT_TRUE(weak_object); DoMarking(MarkingConfig::StackState::kNoHeapPointers); EXPECT_TRUE(weak_object); } { Persistent object = MakeGarbageCollected(GetAllocationHandle()); Persistent parent = MakeGarbageCollected(GetAllocationHandle()); parent->SetWeakChild(object); EXPECT_TRUE(parent->weak_child()); DoMarking(MarkingConfig::StackState::kNoHeapPointers); EXPECT_TRUE(parent->weak_child()); } // Reachable from Member { Persistent parent = MakeGarbageCollected(GetAllocationHandle()); WeakPersistent weak_object( MakeGarbageCollected(GetAllocationHandle())); parent->SetChild(weak_object); EXPECT_TRUE(weak_object); DoMarking(MarkingConfig::StackState::kNoHeapPointers); EXPECT_TRUE(weak_object); } { Persistent parent = MakeGarbageCollected(GetAllocationHandle()); parent->SetChild(MakeGarbageCollected(GetAllocationHandle())); parent->SetWeakChild(parent->child()); EXPECT_TRUE(parent->weak_child()); DoMarking(MarkingConfig::StackState::kNoHeapPointers); EXPECT_TRUE(parent->weak_child()); } // Reachable from stack { GCed* object = MakeGarbageCollected(GetAllocationHandle()); WeakPersistent weak_object(object); EXPECT_TRUE(weak_object); DoMarking(MarkingConfig::StackState::kMayContainHeapPointers); EXPECT_TRUE(weak_object); access(object); } { GCed* object = MakeGarbageCollected(GetAllocationHandle()); Persistent parent = MakeGarbageCollected(GetAllocationHandle()); parent->SetWeakChild(object); EXPECT_TRUE(parent->weak_child()); DoMarking(MarkingConfig::StackState::kMayContainHeapPointers); EXPECT_TRUE(parent->weak_child()); access(object); } } TEST_F(MarkerTest, DeepHierarchyIsMarked) { static constexpr int kHierarchyDepth = 10; Persistent root = MakeGarbageCollected(GetAllocationHandle()); GCed* parent = root; for (int i = 0; i < kHierarchyDepth; ++i) { parent->SetChild(MakeGarbageCollected(GetAllocationHandle())); parent->SetWeakChild(parent->child()); parent = parent->child(); } DoMarking(MarkingConfig::StackState::kNoHeapPointers); EXPECT_TRUE(HeapObjectHeader::FromPayload(root).IsMarked()); parent = root; for (int i = 0; i < kHierarchyDepth; ++i) { EXPECT_TRUE(HeapObjectHeader::FromPayload(parent->child()).IsMarked()); EXPECT_TRUE(parent->weak_child()); parent = parent->child(); } } TEST_F(MarkerTest, NestedObjectsOnStackAreMarked) { GCed* root = MakeGarbageCollected(GetAllocationHandle()); root->SetChild(MakeGarbageCollected(GetAllocationHandle())); root->child()->SetChild(MakeGarbageCollected(GetAllocationHandle())); DoMarking(MarkingConfig::StackState::kMayContainHeapPointers); EXPECT_TRUE(HeapObjectHeader::FromPayload(root).IsMarked()); EXPECT_TRUE(HeapObjectHeader::FromPayload(root->child()).IsMarked()); EXPECT_TRUE(HeapObjectHeader::FromPayload(root->child()->child()).IsMarked()); } namespace { class GCedWithCallback : public GarbageCollected { public: template explicit GCedWithCallback(Callback callback) { callback(this); } void Trace(Visitor*) const {} }; } // namespace TEST_F(MarkerTest, InConstructionObjectIsEventuallyMarkedEmptyStack) { static const Marker::MarkingConfig config = { MarkingConfig::CollectionType::kMajor, MarkingConfig::StackState::kMayContainHeapPointers}; InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get(), config); GCedWithCallback* object = MakeGarbageCollected( GetAllocationHandle(), [marker = marker()](GCedWithCallback* obj) { Member member(obj); marker->VisitorForTesting().Trace(member); }); EXPECT_TRUE(HeapObjectHeader::FromPayload(object).IsMarked()); marker()->FinishMarking(MarkingConfig::StackState::kMayContainHeapPointers); EXPECT_TRUE(HeapObjectHeader::FromPayload(object).IsMarked()); } TEST_F(MarkerTest, InConstructionObjectIsEventuallyMarkedNonEmptyStack) { static const Marker::MarkingConfig config = { MarkingConfig::CollectionType::kMajor, MarkingConfig::StackState::kMayContainHeapPointers}; InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get(), config); MakeGarbageCollected( GetAllocationHandle(), [marker = marker()](GCedWithCallback* obj) { Member member(obj); marker->VisitorForTesting().Trace(member); EXPECT_TRUE(HeapObjectHeader::FromPayload(obj).IsMarked()); marker->FinishMarking( MarkingConfig::StackState::kMayContainHeapPointers); EXPECT_TRUE(HeapObjectHeader::FromPayload(obj).IsMarked()); }); } TEST_F(MarkerTest, SentinelNotClearedOnWeakPersistentHandling) { static const Marker::MarkingConfig config = { MarkingConfig::CollectionType::kMajor, MarkingConfig::StackState::kNoHeapPointers}; InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get(), config); Persistent root = MakeGarbageCollected(GetAllocationHandle()); auto* tmp = MakeGarbageCollected(GetAllocationHandle()); root->SetWeakChild(tmp); marker()->FinishMarking(MarkingConfig::StackState::kNoHeapPointers); root->SetWeakChild(kSentinelPointer); marker()->ProcessWeakness(); EXPECT_EQ(kSentinelPointer, root->weak_child()); } // Incremental Marking class IncrementalMarkingTest : public testing::TestWithHeap { public: using MarkingConfig = Marker::MarkingConfig; static constexpr MarkingConfig IncrementalPreciseMarkingConfig = { MarkingConfig::CollectionType::kMajor, MarkingConfig::StackState::kNoHeapPointers, MarkingConfig::MarkingType::kIncremental}; static constexpr MarkingConfig IncrementalConservativeMarkingConfig = { MarkingConfig::CollectionType::kMajor, MarkingConfig::StackState::kMayContainHeapPointers, MarkingConfig::MarkingType::kIncremental}; void FinishSteps(MarkingConfig::StackState stack_state) { while (!SingleStep(stack_state)) {} } void FinishMarking() { marker_->FinishMarking(MarkingConfig::StackState::kMayContainHeapPointers); marker_->ProcessWeakness(); // Pretend do finish sweeping as StatsCollector verifies that Notify* // methods are called in the right order. Heap::From(GetHeap())->stats_collector()->NotifySweepingCompleted(); } void InitializeMarker(HeapBase& heap, cppgc::Platform* platform, MarkingConfig config) { marker_ = MarkerFactory::CreateAndStartMarking(heap, platform, config); } Marker* marker() const { return marker_.get(); } private: bool SingleStep(MarkingConfig::StackState stack_state) { return marker_->IncrementalMarkingStepForTesting(stack_state); } std::unique_ptr marker_; }; constexpr IncrementalMarkingTest::MarkingConfig IncrementalMarkingTest::IncrementalPreciseMarkingConfig; constexpr IncrementalMarkingTest::MarkingConfig IncrementalMarkingTest::IncrementalConservativeMarkingConfig; TEST_F(IncrementalMarkingTest, RootIsMarkedAfterMarkingStarted) { Persistent root = MakeGarbageCollected(GetAllocationHandle()); EXPECT_FALSE(HeapObjectHeader::FromPayload(root).IsMarked()); InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get(), IncrementalPreciseMarkingConfig); EXPECT_TRUE(HeapObjectHeader::FromPayload(root).IsMarked()); FinishMarking(); } TEST_F(IncrementalMarkingTest, MemberIsMarkedAfterMarkingSteps) { Persistent root = MakeGarbageCollected(GetAllocationHandle()); root->SetChild(MakeGarbageCollected(GetAllocationHandle())); HeapObjectHeader& header = HeapObjectHeader::FromPayload(root->child()); EXPECT_FALSE(header.IsMarked()); InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get(), IncrementalPreciseMarkingConfig); FinishSteps(MarkingConfig::StackState::kNoHeapPointers); EXPECT_TRUE(header.IsMarked()); FinishMarking(); } TEST_F(IncrementalMarkingTest, MemberWithWriteBarrierIsMarkedAfterMarkingSteps) { Persistent root = MakeGarbageCollected(GetAllocationHandle()); InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get(), IncrementalPreciseMarkingConfig); root->SetChild(MakeGarbageCollected(GetAllocationHandle())); HeapObjectHeader& header = HeapObjectHeader::FromPayload(root->child()); EXPECT_FALSE(header.IsMarked()); FinishSteps(MarkingConfig::StackState::kNoHeapPointers); EXPECT_TRUE(header.IsMarked()); FinishMarking(); } namespace { class Holder : public GarbageCollected { public: void Trace(Visitor* visitor) const { visitor->Trace(member_); } Member member_; }; } // namespace TEST_F(IncrementalMarkingTest, IncrementalStepDuringAllocation) { Persistent holder = MakeGarbageCollected(GetAllocationHandle()); InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get(), IncrementalPreciseMarkingConfig); const HeapObjectHeader* header; MakeGarbageCollected( GetAllocationHandle(), [this, &holder, &header](GCedWithCallback* obj) { header = &HeapObjectHeader::FromPayload(obj); holder->member_ = obj; EXPECT_FALSE(header->IsMarked()); FinishSteps(MarkingConfig::StackState::kMayContainHeapPointers); EXPECT_TRUE(header->IsMarked()); }); FinishSteps(MarkingConfig::StackState::kNoHeapPointers); EXPECT_TRUE(header->IsMarked()); FinishMarking(); } TEST_F(IncrementalMarkingTest, MarkingRunsOutOfWorkEventually) { InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get(), IncrementalPreciseMarkingConfig); FinishSteps(MarkingConfig::StackState::kNoHeapPointers); FinishMarking(); } } // namespace internal } // namespace cppgc