// 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 "include/cppgc/internal/write-barrier.h" #include #include #include #include "include/cppgc/internal/pointer-policies.h" #include "src/heap/cppgc/heap-object-header-inl.h" #include "src/heap/cppgc/heap-object-header.h" #include "src/heap/cppgc/marker.h" #include "test/unittests/heap/cppgc/tests.h" #include "testing/gtest/include/gtest/gtest.h" namespace cppgc { namespace internal { namespace { class IncrementalMarkingScope { public: explicit IncrementalMarkingScope(Marker* marker) : marker_(marker) { marker_->StartMarking(kIncrementalConfig); } ~IncrementalMarkingScope() V8_NOEXCEPT { marker_->FinishMarking(kIncrementalConfig); } private: static constexpr Marker::MarkingConfig kIncrementalConfig{ Marker::MarkingConfig::StackState::kNoHeapPointers, Marker::MarkingConfig::MarkingType::kIncremental}; Marker* marker_; }; constexpr Marker::MarkingConfig IncrementalMarkingScope::kIncrementalConfig; class ExpectWriteBarrierFires final : private IncrementalMarkingScope { public: ExpectWriteBarrierFires(Marker* marker, std::initializer_list objects) : IncrementalMarkingScope(marker), marking_worklist_(marker->marking_worklist(), Marker::kMutatorThreadId), write_barrier_worklist_(marker->write_barrier_worklist(), Marker::kMutatorThreadId), objects_(objects) { EXPECT_TRUE(marking_worklist_.IsGlobalPoolEmpty()); EXPECT_TRUE(write_barrier_worklist_.IsGlobalPoolEmpty()); for (void* object : objects) { headers_.push_back(&HeapObjectHeader::FromPayload(object)); EXPECT_FALSE(headers_.back()->IsMarked()); } } ~ExpectWriteBarrierFires() V8_NOEXCEPT { { Marker::MarkingItem item; while (marking_worklist_.Pop(&item)) { auto pos = std::find(objects_.begin(), objects_.end(), item.base_object_payload); if (pos != objects_.end()) objects_.erase(pos); } } { HeapObjectHeader* item; while (write_barrier_worklist_.Pop(&item)) { auto pos = std::find(objects_.begin(), objects_.end(), item->Payload()); if (pos != objects_.end()) objects_.erase(pos); } } EXPECT_TRUE(objects_.empty()); for (auto* header : headers_) { EXPECT_TRUE(header->IsMarked()); header->Unmark(); } EXPECT_TRUE(marking_worklist_.IsGlobalPoolEmpty()); EXPECT_TRUE(write_barrier_worklist_.IsGlobalPoolEmpty()); } private: Marker::MarkingWorklist::View marking_worklist_; Marker::WriteBarrierWorklist::View write_barrier_worklist_; std::vector objects_; std::vector headers_; }; class ExpectNoWriteBarrierFires final : private IncrementalMarkingScope { public: ExpectNoWriteBarrierFires(Marker* marker, std::initializer_list objects) : IncrementalMarkingScope(marker), marking_worklist_(marker->marking_worklist(), Marker::kMutatorThreadId), write_barrier_worklist_(marker->write_barrier_worklist(), Marker::kMutatorThreadId) { EXPECT_TRUE(marking_worklist_.IsGlobalPoolEmpty()); EXPECT_TRUE(write_barrier_worklist_.IsGlobalPoolEmpty()); for (void* object : objects) { auto* header = &HeapObjectHeader::FromPayload(object); headers_.emplace_back(header, header->IsMarked()); } } ~ExpectNoWriteBarrierFires() { EXPECT_TRUE(marking_worklist_.IsGlobalPoolEmpty()); EXPECT_TRUE(write_barrier_worklist_.IsGlobalPoolEmpty()); for (const auto& pair : headers_) { EXPECT_EQ(pair.second, pair.first->IsMarked()); } } private: Marker::MarkingWorklist::View marking_worklist_; Marker::WriteBarrierWorklist::View write_barrier_worklist_; std::vector> headers_; }; class GCed : public GarbageCollected { public: GCed() = default; explicit GCed(GCed* next) : next_(next) {} void Trace(cppgc::Visitor* v) const { v->Trace(next_); } bool IsMarked() const { return HeapObjectHeader::FromPayload(this).IsMarked(); } void set_next(GCed* next) { next_ = next; } GCed* next() const { return next_; } Member& next_ref() { return next_; } private: Member next_ = nullptr; }; } // namespace class WriteBarrierTest : public testing::TestWithHeap { public: WriteBarrierTest() : iheap_(Heap::From(GetHeap())) { GetMarkerRef() = std::make_unique(iheap_); marker_ = GetMarkerRef().get(); } ~WriteBarrierTest() override { marker_->ClearAllWorklistsForTesting(); GetMarkerRef().reset(); } Marker* marker() const { return marker_; } private: Heap* iheap_; Marker* marker_; }; // ============================================================================= // Basic support. ============================================================== // ============================================================================= TEST_F(WriteBarrierTest, EnableDisableIncrementalMarking) { { IncrementalMarkingScope scope(marker()); EXPECT_TRUE(ProcessHeap::IsAnyIncrementalOrConcurrentMarking()); } } TEST_F(WriteBarrierTest, TriggersWhenMarkingIsOn) { auto* object1 = MakeGarbageCollected(GetHeap()); auto* object2 = MakeGarbageCollected(GetHeap()); { ExpectWriteBarrierFires scope(marker(), {object1}); EXPECT_FALSE(object1->IsMarked()); object2->set_next(object1); EXPECT_TRUE(object1->IsMarked()); } } TEST_F(WriteBarrierTest, BailoutWhenMarkingIsOff) { auto* object1 = MakeGarbageCollected(GetHeap()); auto* object2 = MakeGarbageCollected(GetHeap()); EXPECT_FALSE(object1->IsMarked()); object2->set_next(object1); EXPECT_FALSE(object1->IsMarked()); } TEST_F(WriteBarrierTest, BailoutIfMarked) { auto* object1 = MakeGarbageCollected(GetHeap()); auto* object2 = MakeGarbageCollected(GetHeap()); EXPECT_TRUE(HeapObjectHeader::FromPayload(object1).TryMarkAtomic()); { ExpectNoWriteBarrierFires scope(marker(), {object1}); object2->set_next(object1); } } TEST_F(WriteBarrierTest, MemberInitializingStoreNoBarrier) { auto* object1 = MakeGarbageCollected(GetHeap()); { ExpectNoWriteBarrierFires scope(marker(), {object1}); auto* object2 = MakeGarbageCollected(GetHeap(), object1); HeapObjectHeader& object2_header = HeapObjectHeader::FromPayload(object2); EXPECT_FALSE(object2_header.IsMarked()); } } TEST_F(WriteBarrierTest, MemberReferenceAssignMember) { auto* obj = MakeGarbageCollected(GetHeap()); auto* ref_obj = MakeGarbageCollected(GetHeap()); Member& m2 = ref_obj->next_ref(); Member m3(obj); { ExpectWriteBarrierFires scope(marker(), {obj}); m2 = m3; } } TEST_F(WriteBarrierTest, MemberSetSentinelValueNoBarrier) { auto* obj = MakeGarbageCollected(GetHeap()); Member& m = obj->next_ref(); { ExpectNoWriteBarrierFires scope(marker(), {}); m = kSentinelPointer; } } TEST_F(WriteBarrierTest, MemberCopySentinelValueNoBarrier) { auto* obj1 = MakeGarbageCollected(GetHeap()); Member& m1 = obj1->next_ref(); m1 = kSentinelPointer; { ExpectNoWriteBarrierFires scope(marker(), {}); auto* obj2 = MakeGarbageCollected(GetHeap()); obj2->next_ref() = m1; } } // ============================================================================= // Mixin support. ============================================================== // ============================================================================= namespace { class Mixin : public GarbageCollectedMixin { public: void Trace(cppgc::Visitor* visitor) const override { visitor->Trace(next_); } virtual void Bar() {} protected: Member next_; }; class ClassWithVirtual { protected: virtual void Foo() {} }; class Child : public GarbageCollected, public ClassWithVirtual, public Mixin { USING_GARBAGE_COLLECTED_MIXIN(); public: Child() : ClassWithVirtual(), Mixin() {} ~Child() = default; void Trace(cppgc::Visitor* visitor) const override { Mixin::Trace(visitor); } void Foo() override {} void Bar() override {} }; class ParentWithMixinPointer : public GarbageCollected { public: ParentWithMixinPointer() = default; void set_mixin(Mixin* mixin) { mixin_ = mixin; } virtual void Trace(cppgc::Visitor* visitor) const { visitor->Trace(mixin_); } protected: Member mixin_; }; } // namespace TEST_F(WriteBarrierTest, WriteBarrierOnUnmarkedMixinApplication) { ParentWithMixinPointer* parent = MakeGarbageCollected(GetHeap()); auto* child = MakeGarbageCollected(GetHeap()); Mixin* mixin = static_cast(child); EXPECT_NE(static_cast(child), static_cast(mixin)); { ExpectWriteBarrierFires scope(marker(), {child}); parent->set_mixin(mixin); } } TEST_F(WriteBarrierTest, NoWriteBarrierOnMarkedMixinApplication) { ParentWithMixinPointer* parent = MakeGarbageCollected(GetHeap()); auto* child = MakeGarbageCollected(GetHeap()); EXPECT_TRUE(HeapObjectHeader::FromPayload(child).TryMarkAtomic()); Mixin* mixin = static_cast(child); EXPECT_NE(static_cast(child), static_cast(mixin)); { ExpectNoWriteBarrierFires scope(marker(), {child}); parent->set_mixin(mixin); } } } // namespace internal } // namespace cppgc