// 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.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(MarkerBase* marker) : marker_(marker) {} ~IncrementalMarkingScope() V8_NOEXCEPT { marker_->FinishMarking(kIncrementalConfig.stack_state); } static constexpr Marker::MarkingConfig kIncrementalConfig{ Marker::MarkingConfig::CollectionType::kMajor, Marker::MarkingConfig::StackState::kNoHeapPointers, Marker::MarkingConfig::MarkingType::kIncremental}; private: MarkerBase* marker_; }; constexpr Marker::MarkingConfig IncrementalMarkingScope::kIncrementalConfig; class ExpectWriteBarrierFires final : private IncrementalMarkingScope { public: ExpectWriteBarrierFires(MarkerBase* marker, std::initializer_list objects) : IncrementalMarkingScope(marker), marking_worklist_(marker->MarkingStateForTesting().marking_worklist()), write_barrier_worklist_( marker->MarkingStateForTesting().write_barrier_worklist()), objects_(objects) { EXPECT_TRUE(marking_worklist_.IsGlobalEmpty()); EXPECT_TRUE(write_barrier_worklist_.IsGlobalEmpty()); for (void* object : objects) { headers_.push_back(&HeapObjectHeader::FromPayload(object)); EXPECT_FALSE(headers_.back()->IsMarked()); } } ~ExpectWriteBarrierFires() V8_NOEXCEPT { { MarkingWorklists::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_.IsGlobalEmpty()); EXPECT_TRUE(write_barrier_worklist_.IsGlobalEmpty()); } private: MarkingWorklists::MarkingWorklist::Local& marking_worklist_; MarkingWorklists::WriteBarrierWorklist::Local& write_barrier_worklist_; std::vector objects_; std::vector headers_; }; class ExpectNoWriteBarrierFires final : private IncrementalMarkingScope { public: ExpectNoWriteBarrierFires(MarkerBase* marker, std::initializer_list objects) : IncrementalMarkingScope(marker), marking_worklist_(marker->MarkingStateForTesting().marking_worklist()), write_barrier_worklist_( marker->MarkingStateForTesting().write_barrier_worklist()) { EXPECT_TRUE(marking_worklist_.IsGlobalEmpty()); EXPECT_TRUE(write_barrier_worklist_.IsGlobalEmpty()); for (void* object : objects) { auto* header = &HeapObjectHeader::FromPayload(object); headers_.emplace_back(header, header->IsMarked()); } } ~ExpectNoWriteBarrierFires() { EXPECT_TRUE(marking_worklist_.IsGlobalEmpty()); EXPECT_TRUE(write_barrier_worklist_.IsGlobalEmpty()); for (const auto& pair : headers_) { EXPECT_EQ(pair.second, pair.first->IsMarked()); } } private: MarkingWorklists::MarkingWorklist::Local& marking_worklist_; MarkingWorklists::WriteBarrierWorklist::Local& 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() : internal_heap_(Heap::From(GetHeap())) { GetMarkerRef() = MarkerFactory::CreateAndStartMarking( *internal_heap_, GetPlatformHandle().get(), IncrementalMarkingScope::kIncrementalConfig); marker_ = GetMarkerRef().get(); } ~WriteBarrierTest() override { marker_->ClearAllWorklistsForTesting(); GetMarkerRef().reset(); } MarkerBase* marker() const { return marker_; } private: Heap* internal_heap_; MarkerBase* marker_; }; class NoWriteBarrierTest : public testing::TestWithHeap {}; // ============================================================================= // Basic support. ============================================================== // ============================================================================= TEST_F(WriteBarrierTest, EnableDisableIncrementalMarking) { { IncrementalMarkingScope scope(marker()); EXPECT_TRUE(ProcessHeap::IsAnyIncrementalOrConcurrentMarking()); } } TEST_F(WriteBarrierTest, TriggersWhenMarkingIsOn) { auto* object1 = MakeGarbageCollected(GetAllocationHandle()); auto* object2 = MakeGarbageCollected(GetAllocationHandle()); { ExpectWriteBarrierFires scope(marker(), {object1}); EXPECT_FALSE(object1->IsMarked()); object2->set_next(object1); EXPECT_TRUE(object1->IsMarked()); } } TEST_F(NoWriteBarrierTest, BailoutWhenMarkingIsOff) { auto* object1 = MakeGarbageCollected(GetAllocationHandle()); auto* object2 = MakeGarbageCollected(GetAllocationHandle()); EXPECT_FALSE(object1->IsMarked()); object2->set_next(object1); EXPECT_FALSE(object1->IsMarked()); } TEST_F(WriteBarrierTest, BailoutIfMarked) { auto* object1 = MakeGarbageCollected(GetAllocationHandle()); auto* object2 = MakeGarbageCollected(GetAllocationHandle()); EXPECT_TRUE(HeapObjectHeader::FromPayload(object1).TryMarkAtomic()); { ExpectNoWriteBarrierFires scope(marker(), {object1}); object2->set_next(object1); } } TEST_F(WriteBarrierTest, MemberInitializingStoreNoBarrier) { auto* object1 = MakeGarbageCollected(GetAllocationHandle()); { ExpectNoWriteBarrierFires scope(marker(), {object1}); auto* object2 = MakeGarbageCollected(GetAllocationHandle(), object1); HeapObjectHeader& object2_header = HeapObjectHeader::FromPayload(object2); EXPECT_FALSE(object2_header.IsMarked()); } } TEST_F(WriteBarrierTest, MemberReferenceAssignMember) { auto* obj = MakeGarbageCollected(GetAllocationHandle()); auto* ref_obj = MakeGarbageCollected(GetAllocationHandle()); Member& m2 = ref_obj->next_ref(); Member m3(obj); { ExpectWriteBarrierFires scope(marker(), {obj}); m2 = m3; } } TEST_F(WriteBarrierTest, MemberSetSentinelValueNoBarrier) { auto* obj = MakeGarbageCollected(GetAllocationHandle()); Member& m = obj->next_ref(); { ExpectNoWriteBarrierFires scope(marker(), {}); m = kSentinelPointer; } } TEST_F(WriteBarrierTest, MemberCopySentinelValueNoBarrier) { auto* obj1 = MakeGarbageCollected(GetAllocationHandle()); Member& m1 = obj1->next_ref(); m1 = kSentinelPointer; { ExpectNoWriteBarrierFires scope(marker(), {}); auto* obj2 = MakeGarbageCollected(GetAllocationHandle()); 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 { 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(GetAllocationHandle()); auto* child = MakeGarbageCollected(GetAllocationHandle()); 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(GetAllocationHandle()); auto* child = MakeGarbageCollected(GetAllocationHandle()); 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