diff options
Diffstat (limited to 'deps/v8/test/unittests/heap/cppgc')
23 files changed, 1371 insertions, 277 deletions
diff --git a/deps/v8/test/unittests/heap/cppgc/compactor-unittest.cc b/deps/v8/test/unittests/heap/cppgc/compactor-unittest.cc new file mode 100644 index 0000000000..92ae9dc6b6 --- /dev/null +++ b/deps/v8/test/unittests/heap/cppgc/compactor-unittest.cc @@ -0,0 +1,250 @@ +// 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/compactor.h" + +#include "include/cppgc/allocation.h" +#include "include/cppgc/custom-space.h" +#include "include/cppgc/persistent.h" +#include "src/heap/cppgc/heap-object-header.h" +#include "src/heap/cppgc/heap-page.h" +#include "src/heap/cppgc/marker.h" +#include "src/heap/cppgc/stats-collector.h" +#include "test/unittests/heap/cppgc/tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace cppgc { + +class CompactableCustomSpace : public CustomSpace<CompactableCustomSpace> { + public: + static constexpr size_t kSpaceIndex = 0; + static constexpr bool kSupportsCompaction = true; +}; + +namespace internal { + +namespace { + +struct CompactableGCed : public GarbageCollected<CompactableGCed> { + public: + ~CompactableGCed() { ++g_destructor_callcount; } + void Trace(Visitor* visitor) const { + visitor->Trace(other); + visitor->RegisterMovableReference(other.GetSlotForTesting()); + } + static size_t g_destructor_callcount; + Member<CompactableGCed> other; + size_t id = 0; +}; +// static +size_t CompactableGCed::g_destructor_callcount = 0; + +template <int kNumObjects> +struct CompactableHolder + : public GarbageCollected<CompactableHolder<kNumObjects>> { + public: + explicit CompactableHolder(cppgc::AllocationHandle& allocation_handle) { + for (int i = 0; i < kNumObjects; ++i) + objects[i] = MakeGarbageCollected<CompactableGCed>(allocation_handle); + } + + void Trace(Visitor* visitor) const { + for (int i = 0; i < kNumObjects; ++i) { + visitor->Trace(objects[i]); + visitor->RegisterMovableReference(objects[i].GetSlotForTesting()); + } + } + Member<CompactableGCed> objects[kNumObjects]; +}; + +class CompactorTest : public testing::TestWithPlatform { + public: + CompactorTest() { + Heap::HeapOptions options; + options.custom_spaces.emplace_back( + std::make_unique<CompactableCustomSpace>()); + heap_ = Heap::Create(platform_, std::move(options)); + } + + void StartCompaction() { + compactor().EnableForNextGCForTesting(); + compactor().InitializeIfShouldCompact( + GarbageCollector::Config::MarkingType::kIncremental, + GarbageCollector::Config::StackState::kNoHeapPointers); + EXPECT_TRUE(compactor().IsEnabledForTesting()); + } + + void CancelCompaction() { + bool cancelled = compactor().CancelIfShouldNotCompact( + GarbageCollector::Config::MarkingType::kAtomic, + GarbageCollector::Config::StackState::kMayContainHeapPointers); + EXPECT_TRUE(cancelled); + } + + void FinishCompaction() { compactor().CompactSpacesIfEnabled(); } + + void StartGC() { + CompactableGCed::g_destructor_callcount = 0u; + StartCompaction(); + heap()->StartIncrementalGarbageCollection( + GarbageCollector::Config::PreciseIncrementalConfig()); + } + + void EndGC() { + heap()->marker()->FinishMarking( + GarbageCollector::Config::StackState::kNoHeapPointers); + FinishCompaction(); + // Sweeping also verifies the object start bitmap. + const Sweeper::SweepingConfig sweeping_config{ + Sweeper::SweepingConfig::SweepingType::kAtomic, + Sweeper::SweepingConfig::CompactableSpaceHandling::kIgnore}; + heap()->sweeper().Start(sweeping_config); + } + + Heap* heap() { return Heap::From(heap_.get()); } + cppgc::AllocationHandle& GetAllocationHandle() { + return heap_->GetAllocationHandle(); + } + Compactor& compactor() { return heap()->compactor(); } + + private: + std::unique_ptr<cppgc::Heap> heap_; +}; + +} // namespace + +} // namespace internal + +template <> +struct SpaceTrait<internal::CompactableGCed> { + using Space = CompactableCustomSpace; +}; + +namespace internal { + +TEST_F(CompactorTest, NothingToCompact) { + StartCompaction(); + FinishCompaction(); +} + +TEST_F(CompactorTest, CancelledNothingToCompact) { + StartCompaction(); + CancelCompaction(); +} + +TEST_F(CompactorTest, NonEmptySpaceAllLive) { + static constexpr int kNumObjects = 10; + Persistent<CompactableHolder<kNumObjects>> holder = + MakeGarbageCollected<CompactableHolder<kNumObjects>>( + GetAllocationHandle(), GetAllocationHandle()); + CompactableGCed* references[kNumObjects] = {nullptr}; + for (int i = 0; i < kNumObjects; ++i) { + references[i] = holder->objects[i]; + } + StartGC(); + EndGC(); + EXPECT_EQ(0u, CompactableGCed::g_destructor_callcount); + for (int i = 0; i < kNumObjects; ++i) { + EXPECT_EQ(holder->objects[i], references[i]); + } +} + +TEST_F(CompactorTest, NonEmptySpaceAllDead) { + static constexpr int kNumObjects = 10; + Persistent<CompactableHolder<kNumObjects>> holder = + MakeGarbageCollected<CompactableHolder<kNumObjects>>( + GetAllocationHandle(), GetAllocationHandle()); + CompactableGCed::g_destructor_callcount = 0u; + StartGC(); + for (int i = 0; i < kNumObjects; ++i) { + holder->objects[i] = nullptr; + } + EndGC(); + EXPECT_EQ(10u, CompactableGCed::g_destructor_callcount); +} + +TEST_F(CompactorTest, NonEmptySpaceHalfLive) { + static constexpr int kNumObjects = 10; + Persistent<CompactableHolder<kNumObjects>> holder = + MakeGarbageCollected<CompactableHolder<kNumObjects>>( + GetAllocationHandle(), GetAllocationHandle()); + CompactableGCed* references[kNumObjects] = {nullptr}; + for (int i = 0; i < kNumObjects; ++i) { + references[i] = holder->objects[i]; + } + StartGC(); + for (int i = 0; i < kNumObjects; i += 2) { + holder->objects[i] = nullptr; + } + EndGC(); + // Half of object were destroyed. + EXPECT_EQ(5u, CompactableGCed::g_destructor_callcount); + // Remaining objects are compacted. + for (int i = 1; i < kNumObjects; i += 2) { + EXPECT_EQ(holder->objects[i], references[i / 2]); + } +} + +TEST_F(CompactorTest, CompactAcrossPages) { + Persistent<CompactableHolder<1>> holder = + MakeGarbageCollected<CompactableHolder<1>>(GetAllocationHandle(), + GetAllocationHandle()); + CompactableGCed* reference = holder->objects[0]; + static constexpr size_t kObjectsPerPage = + kPageSize / (sizeof(CompactableGCed) + sizeof(HeapObjectHeader)); + for (size_t i = 0; i < kObjectsPerPage; ++i) { + holder->objects[0] = + MakeGarbageCollected<CompactableGCed>(GetAllocationHandle()); + } + // Last allocated object should be on a new page. + EXPECT_NE(reference, holder->objects[0]); + EXPECT_NE(BasePage::FromInnerAddress(heap(), reference), + BasePage::FromInnerAddress(heap(), holder->objects[0].Get())); + StartGC(); + EndGC(); + // Half of object were destroyed. + EXPECT_EQ(kObjectsPerPage, CompactableGCed::g_destructor_callcount); + EXPECT_EQ(reference, holder->objects[0]); +} + +TEST_F(CompactorTest, InteriorSlotToPreviousObject) { + static constexpr int kNumObjects = 3; + Persistent<CompactableHolder<kNumObjects>> holder = + MakeGarbageCollected<CompactableHolder<kNumObjects>>( + GetAllocationHandle(), GetAllocationHandle()); + CompactableGCed* references[kNumObjects] = {nullptr}; + for (int i = 0; i < kNumObjects; ++i) { + references[i] = holder->objects[i]; + } + holder->objects[2]->other = holder->objects[1]; + holder->objects[1] = nullptr; + holder->objects[0] = nullptr; + StartGC(); + EndGC(); + EXPECT_EQ(1u, CompactableGCed::g_destructor_callcount); + EXPECT_EQ(references[1], holder->objects[2]); + EXPECT_EQ(references[0], holder->objects[2]->other); +} + +TEST_F(CompactorTest, InteriorSlotToNextObject) { + static constexpr int kNumObjects = 3; + Persistent<CompactableHolder<kNumObjects>> holder = + MakeGarbageCollected<CompactableHolder<kNumObjects>>( + GetAllocationHandle(), GetAllocationHandle()); + CompactableGCed* references[kNumObjects] = {nullptr}; + for (int i = 0; i < kNumObjects; ++i) { + references[i] = holder->objects[i]; + } + holder->objects[1]->other = holder->objects[2]; + holder->objects[2] = nullptr; + holder->objects[0] = nullptr; + StartGC(); + EndGC(); + EXPECT_EQ(1u, CompactableGCed::g_destructor_callcount); + EXPECT_EQ(references[0], holder->objects[1]); + EXPECT_EQ(references[1], holder->objects[1]->other); +} + +} // namespace internal +} // namespace cppgc diff --git a/deps/v8/test/unittests/heap/cppgc/concurrent-marking-unittest.cc b/deps/v8/test/unittests/heap/cppgc/concurrent-marking-unittest.cc index b39a545b7b..4da9870221 100644 --- a/deps/v8/test/unittests/heap/cppgc/concurrent-marking-unittest.cc +++ b/deps/v8/test/unittests/heap/cppgc/concurrent-marking-unittest.cc @@ -16,51 +16,17 @@ namespace cppgc { namespace internal { -#if defined(THREAD_SANITIZER) - namespace { -class GCed : public GarbageCollected<GCed> { - public: - void Trace(cppgc::Visitor* visitor) const { visitor->Trace(child_); } - - Member<GCed> child_; -}; - -class GCedWithCallback : public GarbageCollected<GCedWithCallback> { - public: - template <typename Callback> - explicit GCedWithCallback(Callback callback) { - callback(this); - } - - void Trace(cppgc::Visitor* visitor) const { visitor->Trace(child_); } - - Member<GCedWithCallback> child_; -}; - -class Mixin : public GarbageCollectedMixin { - public: - void Trace(cppgc::Visitor* visitor) const { visitor->Trace(child_); } - - Member<Mixin> child_; -}; - -class GCedWithMixin : public GarbageCollected<GCedWithMixin>, public Mixin { - public: - void Trace(cppgc::Visitor* visitor) const { Mixin::Trace(visitor); } -}; - -template <typename T> -class GCedHolder : public GarbageCollected<GCedHolder<T>> { - public: - void Trace(cppgc::Visitor* visitor) const { visitor->Trace(object_); } - - Member<T> object_; -}; - class ConcurrentMarkingTest : public testing::TestWithHeap { public: +#if defined(THREAD_SANITIZER) + // Use more iteration on tsan builds to expose data races. + static constexpr int kNumStep = 1000; +#else + static constexpr int kNumStep = 10; +#endif // defined(THREAD_SANITIZER) + using Config = Heap::Config; static constexpr Config ConcurrentPreciseConfig = { Config::CollectionType::kMajor, Config::StackState::kNoHeapPointers, @@ -95,16 +61,52 @@ class ConcurrentMarkingTest : public testing::TestWithHeap { constexpr ConcurrentMarkingTest::Config ConcurrentMarkingTest::ConcurrentPreciseConfig; +template <typename T> +struct GCedHolder : public GarbageCollected<GCedHolder<T>> { + void Trace(cppgc::Visitor* visitor) const { visitor->Trace(object); } + Member<T> object; +}; + +class GCed : public GarbageCollected<GCed> { + public: + void Trace(cppgc::Visitor* visitor) const { visitor->Trace(child_); } + + Member<GCed> child_; +}; + +class GCedWithCallback : public GarbageCollected<GCedWithCallback> { + public: + template <typename Callback> + explicit GCedWithCallback(Callback callback) { + callback(this); + } + + void Trace(cppgc::Visitor* visitor) const { visitor->Trace(child_); } + + Member<GCedWithCallback> child_; +}; + +class Mixin : public GarbageCollectedMixin { + public: + void Trace(cppgc::Visitor* visitor) const { visitor->Trace(child_); } + + Member<Mixin> child_; +}; + +class GCedWithMixin : public GarbageCollected<GCedWithMixin>, public Mixin { + public: + void Trace(cppgc::Visitor* visitor) const { Mixin::Trace(visitor); } +}; + } // namespace // The following tests below check for data races during concurrent marking. TEST_F(ConcurrentMarkingTest, MarkingObjects) { - static constexpr int kNumStep = 1000; StartConcurrentGC(); Persistent<GCedHolder<GCed>> root = MakeGarbageCollected<GCedHolder<GCed>>(GetAllocationHandle()); - Member<GCed>* last_object = &root->object_; + Member<GCed>* last_object = &root->object; for (int i = 0; i < kNumStep; ++i) { for (int j = 0; j < kNumStep; ++j) { *last_object = MakeGarbageCollected<GCed>(GetAllocationHandle()); @@ -117,11 +119,10 @@ TEST_F(ConcurrentMarkingTest, MarkingObjects) { } TEST_F(ConcurrentMarkingTest, MarkingInConstructionObjects) { - static constexpr int kNumStep = 1000; StartConcurrentGC(); Persistent<GCedHolder<GCedWithCallback>> root = MakeGarbageCollected<GCedHolder<GCedWithCallback>>(GetAllocationHandle()); - Member<GCedWithCallback>* last_object = &root->object_; + Member<GCedWithCallback>* last_object = &root->object; for (int i = 0; i < kNumStep; ++i) { for (int j = 0; j < kNumStep; ++j) { MakeGarbageCollected<GCedWithCallback>( @@ -137,11 +138,10 @@ TEST_F(ConcurrentMarkingTest, MarkingInConstructionObjects) { } TEST_F(ConcurrentMarkingTest, MarkingMixinObjects) { - static constexpr int kNumStep = 1000; StartConcurrentGC(); Persistent<GCedHolder<Mixin>> root = MakeGarbageCollected<GCedHolder<Mixin>>(GetAllocationHandle()); - Member<Mixin>* last_object = &root->object_; + Member<Mixin>* last_object = &root->object; for (int i = 0; i < kNumStep; ++i) { for (int j = 0; j < kNumStep; ++j) { *last_object = MakeGarbageCollected<GCedWithMixin>(GetAllocationHandle()); @@ -153,7 +153,58 @@ TEST_F(ConcurrentMarkingTest, MarkingMixinObjects) { FinishGC(); } -#endif // defined(THREAD_SANITIZER) +namespace { + +struct ConcurrentlyTraceable : public GarbageCollected<ConcurrentlyTraceable> { + static size_t trace_counter; + void Trace(Visitor*) const { ++trace_counter; } +}; +size_t ConcurrentlyTraceable::trace_counter = 0; + +struct NotConcurrentlyTraceable + : public GarbageCollected<NotConcurrentlyTraceable> { + static size_t trace_counter; + void Trace(Visitor* visitor) const { + if (visitor->DeferTraceToMutatorThreadIfConcurrent( + this, + [](Visitor*, const void*) { + ++NotConcurrentlyTraceable::trace_counter; + }, + sizeof(NotConcurrentlyTraceable))) + return; + ++trace_counter; + } +}; +size_t NotConcurrentlyTraceable::trace_counter = 0; + +} // namespace + +TEST_F(ConcurrentMarkingTest, ConcurrentlyTraceableObjectIsTracedConcurrently) { + Persistent<GCedHolder<ConcurrentlyTraceable>> root = + MakeGarbageCollected<GCedHolder<ConcurrentlyTraceable>>( + GetAllocationHandle()); + root->object = + MakeGarbageCollected<ConcurrentlyTraceable>(GetAllocationHandle()); + EXPECT_EQ(0u, ConcurrentlyTraceable::trace_counter); + StartConcurrentGC(); + GetMarkerRef()->WaitForConcurrentMarkingForTesting(); + EXPECT_NE(0u, ConcurrentlyTraceable::trace_counter); + FinishGC(); +} + +TEST_F(ConcurrentMarkingTest, + NotConcurrentlyTraceableObjectIsNotTracedConcurrently) { + Persistent<GCedHolder<NotConcurrentlyTraceable>> root = + MakeGarbageCollected<GCedHolder<NotConcurrentlyTraceable>>( + GetAllocationHandle()); + root->object = + MakeGarbageCollected<NotConcurrentlyTraceable>(GetAllocationHandle()); + EXPECT_EQ(0u, NotConcurrentlyTraceable::trace_counter); + StartConcurrentGC(); + GetMarkerRef()->WaitForConcurrentMarkingForTesting(); + EXPECT_EQ(0u, NotConcurrentlyTraceable::trace_counter); + FinishGC(); +} } // namespace internal } // namespace cppgc diff --git a/deps/v8/test/unittests/heap/cppgc/concurrent-sweeper-unittest.cc b/deps/v8/test/unittests/heap/cppgc/concurrent-sweeper-unittest.cc index 3794adce25..b1cdc5d8fc 100644 --- a/deps/v8/test/unittests/heap/cppgc/concurrent-sweeper-unittest.cc +++ b/deps/v8/test/unittests/heap/cppgc/concurrent-sweeper-unittest.cc @@ -61,6 +61,8 @@ class NonFinalizable : public GarbageCollected<NonFinalizable<Size>> { using NormalNonFinalizable = NonFinalizable<32>; using LargeNonFinalizable = NonFinalizable<kLargeObjectSizeThreshold * 2>; +} // namespace + class ConcurrentSweeperTest : public testing::TestWithHeap { public: ConcurrentSweeperTest() { g_destructor_callcount = 0; } @@ -73,7 +75,16 @@ class ConcurrentSweeperTest : public testing::TestWithHeap { heap->stats_collector()->NotifyMarkingStarted(); heap->stats_collector()->NotifyMarkingCompleted(0); Sweeper& sweeper = heap->sweeper(); - sweeper.Start(Sweeper::Config::kIncrementalAndConcurrent); + const Sweeper::SweepingConfig sweeping_config{ + Sweeper::SweepingConfig::SweepingType::kIncrementalAndConcurrent, + Sweeper::SweepingConfig::CompactableSpaceHandling::kSweep}; + sweeper.Start(sweeping_config); + } + + void WaitForConcurrentSweeping() { + Heap* heap = Heap::From(GetHeap()); + Sweeper& sweeper = heap->sweeper(); + sweeper.WaitForConcurrentSweepingForTesting(); } void FinishSweeping() { @@ -126,8 +137,6 @@ class ConcurrentSweeperTest : public testing::TestWithHeap { } }; -} // namespace - TEST_F(ConcurrentSweeperTest, BackgroundSweepOfNormalPage) { // Non finalizable objects are swept right away. using GCedType = NormalNonFinalizable; @@ -145,7 +154,7 @@ TEST_F(ConcurrentSweeperTest, BackgroundSweepOfNormalPage) { StartSweeping(); // Wait for concurrent sweeping to finish. - GetPlatform().WaitAllBackgroundTasks(); + WaitForConcurrentSweeping(); #if !defined(CPPGC_YOUNG_GENERATION) // Check that the marked object was unmarked. @@ -184,7 +193,7 @@ TEST_F(ConcurrentSweeperTest, BackgroundSweepOfLargePage) { StartSweeping(); // Wait for concurrent sweeping to finish. - GetPlatform().WaitAllBackgroundTasks(); + WaitForConcurrentSweeping(); #if !defined(CPPGC_YOUNG_GENERATION) // Check that the marked object was unmarked. @@ -224,7 +233,7 @@ TEST_F(ConcurrentSweeperTest, DeferredFinalizationOfNormalPage) { StartSweeping(); // Wait for concurrent sweeping to finish. - GetPlatform().WaitAllBackgroundTasks(); + WaitForConcurrentSweeping(); // Check that pages are not returned right away. for (auto* page : pages) { @@ -256,7 +265,7 @@ TEST_F(ConcurrentSweeperTest, DeferredFinalizationOfLargePage) { StartSweeping(); // Wait for concurrent sweeping to finish. - GetPlatform().WaitAllBackgroundTasks(); + WaitForConcurrentSweeping(); // Check that the page is not returned to the space. EXPECT_EQ(space->end(), std::find(space->begin(), space->end(), page)); @@ -302,7 +311,7 @@ TEST_F(ConcurrentSweeperTest, IncrementalSweeping) { EXPECT_TRUE(marked_large_header.IsMarked()); // Wait for incremental sweeper to finish. - GetPlatform().WaitAllForegroundTasks(); + GetPlatform().RunAllForegroundTasks(); EXPECT_EQ(2u, g_destructor_callcount); #if !defined(CPPGC_YOUNG_GENERATION) diff --git a/deps/v8/test/unittests/heap/cppgc/cross-thread-persistent-unittest.cc b/deps/v8/test/unittests/heap/cppgc/cross-thread-persistent-unittest.cc new file mode 100644 index 0000000000..3a9dc91000 --- /dev/null +++ b/deps/v8/test/unittests/heap/cppgc/cross-thread-persistent-unittest.cc @@ -0,0 +1,101 @@ +// 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/cross-thread-persistent.h" + +#include "include/cppgc/allocation.h" +#include "src/base/platform/condition-variable.h" +#include "src/base/platform/mutex.h" +#include "src/base/platform/platform.h" +#include "test/unittests/heap/cppgc/tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace cppgc { +namespace internal { + +namespace { + +struct GCed final : GarbageCollected<GCed> { + static size_t destructor_call_count; + GCed() { destructor_call_count = 0; } + ~GCed() { destructor_call_count++; } + virtual void Trace(cppgc::Visitor*) const {} + int a = 0; +}; +size_t GCed::destructor_call_count = 0; + +class Runner final : public v8::base::Thread { + public: + template <typename Callback> + explicit Runner(Callback callback) + : Thread(v8::base::Thread::Options("CrossThreadPersistent Thread")), + callback_(callback) {} + + void Run() final { callback_(); } + + private: + std::function<void()> callback_; +}; + +} // namespace + +class CrossThreadPersistentTest : public testing::TestWithHeap {}; + +TEST_F(CrossThreadPersistentTest, RetainStronglyOnDifferentThread) { + subtle::CrossThreadPersistent<GCed> holder = + MakeGarbageCollected<GCed>(GetAllocationHandle()); + { + Runner runner([obj = std::move(holder)]() {}); + EXPECT_FALSE(holder); + EXPECT_EQ(0u, GCed::destructor_call_count); + PreciseGC(); + EXPECT_EQ(0u, GCed::destructor_call_count); + runner.StartSynchronously(); + runner.Join(); + } + EXPECT_EQ(0u, GCed::destructor_call_count); + PreciseGC(); + EXPECT_EQ(1u, GCed::destructor_call_count); +} + +TEST_F(CrossThreadPersistentTest, RetainWeaklyOnDifferentThread) { + subtle::WeakCrossThreadPersistent<GCed> in = + MakeGarbageCollected<GCed>(GetAllocationHandle()); + // Set up |out| with an object that is always retained to ensure that the + // different thread indeed moves back an empty handle. + Persistent<GCed> out_holder = + MakeGarbageCollected<GCed>(GetAllocationHandle()); + subtle::WeakCrossThreadPersistent<GCed> out = *out_holder; + { + Persistent<GCed> temporary_holder = *in; + Runner runner([obj = std::move(in), &out]() { out = std::move(obj); }); + EXPECT_FALSE(in); + EXPECT_TRUE(out); + EXPECT_EQ(0u, GCed::destructor_call_count); + PreciseGC(); + EXPECT_EQ(0u, GCed::destructor_call_count); + temporary_holder.Clear(); + PreciseGC(); + EXPECT_EQ(1u, GCed::destructor_call_count); + runner.StartSynchronously(); + runner.Join(); + } + EXPECT_FALSE(out); +} + +TEST_F(CrossThreadPersistentTest, DestroyRacingWithGC) { + // Destroy a handle on a different thread while at the same time invoking a + // garbage collection on the original thread. + subtle::CrossThreadPersistent<GCed> holder = + MakeGarbageCollected<GCed>(GetAllocationHandle()); + Runner runner([&obj = holder]() { obj.Clear(); }); + EXPECT_TRUE(holder); + runner.StartSynchronously(); + PreciseGC(); + runner.Join(); + EXPECT_FALSE(holder); +} + +} // namespace internal +} // namespace cppgc diff --git a/deps/v8/test/unittests/heap/cppgc/custom-spaces-unittest.cc b/deps/v8/test/unittests/heap/cppgc/custom-spaces-unittest.cc index 24e7367f67..7e73a73178 100644 --- a/deps/v8/test/unittests/heap/cppgc/custom-spaces-unittest.cc +++ b/deps/v8/test/unittests/heap/cppgc/custom-spaces-unittest.cc @@ -37,8 +37,9 @@ class TestWithHeapWithCustomSpaces : public testing::TestWithPlatform { } void PreciseGC() { - heap_->ForceGarbageCollectionSlow("TestWithHeapWithCustomSpaces", "Testing", - cppgc::Heap::StackState::kNoHeapPointers); + heap_->ForceGarbageCollectionSlow( + ::testing::UnitTest::GetInstance()->current_test_info()->name(), + "Testing", cppgc::Heap::StackState::kNoHeapPointers); } cppgc::Heap* GetHeap() const { return heap_.get(); } @@ -140,4 +141,106 @@ TEST_F(TestWithHeapWithCustomSpaces, SweepCustomSpace) { } } // namespace internal + +// Test custom space compactability. + +class CompactableCustomSpace : public CustomSpace<CompactableCustomSpace> { + public: + static constexpr size_t kSpaceIndex = 0; + static constexpr bool kSupportsCompaction = true; +}; + +class NotCompactableCustomSpace + : public CustomSpace<NotCompactableCustomSpace> { + public: + static constexpr size_t kSpaceIndex = 1; + static constexpr bool kSupportsCompaction = false; +}; + +class DefaultCompactableCustomSpace + : public CustomSpace<DefaultCompactableCustomSpace> { + public: + static constexpr size_t kSpaceIndex = 2; + // By default space are not compactable. +}; + +namespace internal { +namespace { + +class TestWithHeapWithCompactableCustomSpaces + : public testing::TestWithPlatform { + protected: + TestWithHeapWithCompactableCustomSpaces() { + Heap::HeapOptions options; + options.custom_spaces.emplace_back( + std::make_unique<CompactableCustomSpace>()); + options.custom_spaces.emplace_back( + std::make_unique<NotCompactableCustomSpace>()); + options.custom_spaces.emplace_back( + std::make_unique<DefaultCompactableCustomSpace>()); + heap_ = Heap::Create(platform_, std::move(options)); + g_destructor_callcount = 0; + } + + void PreciseGC() { + heap_->ForceGarbageCollectionSlow("TestWithHeapWithCompactableCustomSpaces", + "Testing", + cppgc::Heap::StackState::kNoHeapPointers); + } + + cppgc::Heap* GetHeap() const { return heap_.get(); } + + private: + std::unique_ptr<cppgc::Heap> heap_; +}; + +class CompactableGCed final : public GarbageCollected<CompactableGCed> { + public: + void Trace(Visitor*) const {} +}; +class NotCompactableGCed final : public GarbageCollected<NotCompactableGCed> { + public: + void Trace(Visitor*) const {} +}; +class DefaultCompactableGCed final + : public GarbageCollected<DefaultCompactableGCed> { + public: + void Trace(Visitor*) const {} +}; + +} // namespace +} // namespace internal + +template <> +struct SpaceTrait<internal::CompactableGCed> { + using Space = CompactableCustomSpace; +}; +template <> +struct SpaceTrait<internal::NotCompactableGCed> { + using Space = NotCompactableCustomSpace; +}; +template <> +struct SpaceTrait<internal::DefaultCompactableGCed> { + using Space = DefaultCompactableCustomSpace; +}; + +namespace internal { + +TEST_F(TestWithHeapWithCompactableCustomSpaces, + AllocateOnCompactableCustomSpaces) { + auto* compactable = + MakeGarbageCollected<CompactableGCed>(GetHeap()->GetAllocationHandle()); + auto* not_compactable = MakeGarbageCollected<NotCompactableGCed>( + GetHeap()->GetAllocationHandle()); + auto* default_compactable = MakeGarbageCollected<DefaultCompactableGCed>( + GetHeap()->GetAllocationHandle()); + EXPECT_TRUE(NormalPage::FromPayload(compactable)->space()->is_compactable()); + EXPECT_FALSE( + NormalPage::FromPayload(not_compactable)->space()->is_compactable()); + EXPECT_FALSE( + NormalPage::FromPayload(default_compactable)->space()->is_compactable()); +} + +} // namespace internal + } // namespace cppgc diff --git a/deps/v8/test/unittests/heap/cppgc/ephemeron-pair-unittest.cc b/deps/v8/test/unittests/heap/cppgc/ephemeron-pair-unittest.cc new file mode 100644 index 0000000000..1172eedb86 --- /dev/null +++ b/deps/v8/test/unittests/heap/cppgc/ephemeron-pair-unittest.cc @@ -0,0 +1,112 @@ +// 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/ephemeron-pair.h" + +#include "include/cppgc/allocation.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 GCed : public GarbageCollected<GCed> { + public: + void Trace(cppgc::Visitor*) const {} +}; + +class EphemeronHolder : public GarbageCollected<GCed> { + public: + EphemeronHolder(GCed* key, GCed* value) : ephemeron_pair_(key, value) {} + void Trace(cppgc::Visitor* visitor) const { visitor->Trace(ephemeron_pair_); } + + private: + EphemeronPair<GCed, GCed> ephemeron_pair_; +}; + +class EhpemeronPairTest : public testing::TestWithHeap { + using MarkingConfig = Marker::MarkingConfig; + + static constexpr Marker::MarkingConfig IncrementalPreciseMarkingConfig = { + MarkingConfig::CollectionType::kMajor, + MarkingConfig::StackState::kNoHeapPointers, + MarkingConfig::MarkingType::kIncremental}; + + public: + void FinishSteps() { + while (!SingleStep()) { + } + } + + void FinishMarking() { + marker_->FinishMarking(MarkingConfig::StackState::kNoHeapPointers); + // 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) { + marker_ = MarkerFactory::CreateAndStartMarking<Marker>( + heap, platform, IncrementalPreciseMarkingConfig); + } + + Marker* marker() const { return marker_.get(); } + + private: + bool SingleStep() { + return marker_->IncrementalMarkingStepForTesting( + MarkingConfig::StackState::kNoHeapPointers); + } + + std::unique_ptr<Marker> marker_; +}; + +// static +constexpr Marker::MarkingConfig + EhpemeronPairTest::IncrementalPreciseMarkingConfig; + +} // namespace + +TEST_F(EhpemeronPairTest, ValueMarkedWhenKeyIsMarked) { + GCed* key = MakeGarbageCollected<GCed>(GetAllocationHandle()); + GCed* value = MakeGarbageCollected<GCed>(GetAllocationHandle()); + Persistent<EphemeronHolder> holder = + MakeGarbageCollected<EphemeronHolder>(GetAllocationHandle(), key, value); + HeapObjectHeader::FromPayload(key).TryMarkAtomic(); + InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get()); + FinishMarking(); + EXPECT_TRUE(HeapObjectHeader::FromPayload(value).IsMarked()); +} + +TEST_F(EhpemeronPairTest, ValueNotMarkedWhenKeyIsNotMarked) { + GCed* key = MakeGarbageCollected<GCed>(GetAllocationHandle()); + GCed* value = MakeGarbageCollected<GCed>(GetAllocationHandle()); + Persistent<EphemeronHolder> holder = + MakeGarbageCollected<EphemeronHolder>(GetAllocationHandle(), key, value); + InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get()); + FinishMarking(); + EXPECT_FALSE(HeapObjectHeader::FromPayload(key).IsMarked()); + EXPECT_FALSE(HeapObjectHeader::FromPayload(value).IsMarked()); +} + +TEST_F(EhpemeronPairTest, ValueNotMarkedBeforeKey) { + GCed* key = MakeGarbageCollected<GCed>(GetAllocationHandle()); + GCed* value = MakeGarbageCollected<GCed>(GetAllocationHandle()); + Persistent<EphemeronHolder> holder = + MakeGarbageCollected<EphemeronHolder>(GetAllocationHandle(), key, value); + InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get()); + FinishSteps(); + EXPECT_FALSE(HeapObjectHeader::FromPayload(value).IsMarked()); + HeapObjectHeader::FromPayload(key).TryMarkAtomic(); + FinishMarking(); + EXPECT_TRUE(HeapObjectHeader::FromPayload(value).IsMarked()); +} + +} // namespace internal +} // namespace cppgc diff --git a/deps/v8/test/unittests/heap/cppgc/gc-info-unittest.cc b/deps/v8/test/unittests/heap/cppgc/gc-info-unittest.cc index b45de5cd06..9c48621e10 100644 --- a/deps/v8/test/unittests/heap/cppgc/gc-info-unittest.cc +++ b/deps/v8/test/unittests/heap/cppgc/gc-info-unittest.cc @@ -14,6 +14,12 @@ namespace cppgc { namespace internal { +namespace { + +constexpr GCInfo GetEmptyGCInfo() { return {nullptr, nullptr, nullptr, false}; } + +} // namespace + TEST(GCInfoTableTest, InitialEmpty) { v8::base::PageAllocator page_allocator; GCInfoTable table(&page_allocator); @@ -23,7 +29,7 @@ TEST(GCInfoTableTest, InitialEmpty) { TEST(GCInfoTableTest, ResizeToMaxIndex) { v8::base::PageAllocator page_allocator; GCInfoTable table(&page_allocator); - GCInfo info = {nullptr, nullptr, false}; + GCInfo info = GetEmptyGCInfo(); for (GCInfoIndex i = GCInfoTable::kMinIndex; i < GCInfoTable::kMaxIndex; i++) { GCInfoIndex index = table.RegisterNewGCInfo(info); @@ -34,7 +40,7 @@ TEST(GCInfoTableTest, ResizeToMaxIndex) { TEST(GCInfoTableDeathTest, MoreThanMaxIndexInfos) { v8::base::PageAllocator page_allocator; GCInfoTable table(&page_allocator); - GCInfo info = {nullptr, nullptr, false}; + GCInfo info = GetEmptyGCInfo(); // Create GCInfoTable::kMaxIndex entries. for (GCInfoIndex i = GCInfoTable::kMinIndex; i < GCInfoTable::kMaxIndex; i++) { @@ -46,7 +52,7 @@ TEST(GCInfoTableDeathTest, MoreThanMaxIndexInfos) { TEST(GCInfoTableDeathTest, OldTableAreaIsReadOnly) { v8::base::PageAllocator page_allocator; GCInfoTable table(&page_allocator); - GCInfo info = {nullptr, nullptr, false}; + GCInfo info = GetEmptyGCInfo(); // Use up all slots until limit. GCInfoIndex limit = table.LimitForTesting(); // Bail out if initial limit is already the maximum because of large committed @@ -76,7 +82,7 @@ class ThreadRegisteringGCInfoObjects final : public v8::base::Thread { num_registrations_(num_registrations) {} void Run() final { - GCInfo info = {nullptr, nullptr, false}; + GCInfo info = GetEmptyGCInfo(); for (GCInfoIndex i = 0; i < num_registrations_; i++) { table_->RegisterNewGCInfo(info); } @@ -101,7 +107,7 @@ TEST(GCInfoTableTest, MultiThreadedResizeToMaxIndex) { v8::base::PageAllocator page_allocator; GCInfoTable table(&page_allocator); - GCInfo info = {nullptr, nullptr, false}; + GCInfo info = GetEmptyGCInfo(); for (size_t i = 0; i < main_thread_initialized; i++) { table.RegisterNewGCInfo(info); } diff --git a/deps/v8/test/unittests/heap/cppgc/gc-invoker-unittest.cc b/deps/v8/test/unittests/heap/cppgc/gc-invoker-unittest.cc index 9dc1b8d426..319f6e433d 100644 --- a/deps/v8/test/unittests/heap/cppgc/gc-invoker-unittest.cc +++ b/deps/v8/test/unittests/heap/cppgc/gc-invoker-unittest.cc @@ -105,7 +105,7 @@ TEST(GCInvokerTest, ConservativeGCIsInvokedAsPreciseGCViaPlatform) { EXPECT_CALL(gc, epoch).WillRepeatedly(::testing::Return(0)); EXPECT_CALL(gc, CollectGarbage); invoker.CollectGarbage(GarbageCollector::Config::ConservativeAtomicConfig()); - platform.WaitAllForegroundTasks(); + platform.RunAllForegroundTasks(); } TEST(GCInvokerTest, IncrementalGCIsStarted) { diff --git a/deps/v8/test/unittests/heap/cppgc/heap-object-header-unittest.cc b/deps/v8/test/unittests/heap/cppgc/heap-object-header-unittest.cc index 71df62a72b..2621af2891 100644 --- a/deps/v8/test/unittests/heap/cppgc/heap-object-header-unittest.cc +++ b/deps/v8/test/unittests/heap/cppgc/heap-object-header-unittest.cc @@ -40,8 +40,7 @@ TEST(HeapObjectHeaderTest, GetGCInfoIndex) { constexpr size_t kSize = kAllocationGranularity; HeapObjectHeader header(kSize, kGCInfoIndex); EXPECT_EQ(kGCInfoIndex, header.GetGCInfoIndex()); - EXPECT_EQ(kGCInfoIndex, - header.GetGCInfoIndex<HeapObjectHeader::AccessMode::kAtomic>()); + EXPECT_EQ(kGCInfoIndex, header.GetGCInfoIndex<AccessMode::kAtomic>()); } TEST(HeapObjectHeaderTest, GetSize) { @@ -49,7 +48,7 @@ TEST(HeapObjectHeaderTest, GetSize) { constexpr size_t kSize = kAllocationGranularity * 23; HeapObjectHeader header(kSize, kGCInfoIndex); EXPECT_EQ(kSize, header.GetSize()); - EXPECT_EQ(kSize, header.GetSize<HeapObjectHeader::AccessMode::kAtomic>()); + EXPECT_EQ(kSize, header.GetSize<AccessMode::kAtomic>()); } TEST(HeapObjectHeaderTest, IsLargeObject) { @@ -57,13 +56,10 @@ TEST(HeapObjectHeaderTest, IsLargeObject) { constexpr size_t kSize = kAllocationGranularity * 23; HeapObjectHeader header(kSize, kGCInfoIndex); EXPECT_EQ(false, header.IsLargeObject()); - EXPECT_EQ(false, - header.IsLargeObject<HeapObjectHeader::AccessMode::kAtomic>()); + EXPECT_EQ(false, header.IsLargeObject<AccessMode::kAtomic>()); HeapObjectHeader large_header(0, kGCInfoIndex + 1); EXPECT_EQ(true, large_header.IsLargeObject()); - EXPECT_EQ( - true, - large_header.IsLargeObject<HeapObjectHeader::AccessMode::kAtomic>()); + EXPECT_EQ(true, large_header.IsLargeObject<AccessMode::kAtomic>()); } TEST(HeapObjectHeaderTest, MarkObjectAsFullyConstructed) { @@ -110,7 +106,7 @@ TEST(HeapObjectHeaderTest, Unmark) { EXPECT_FALSE(header2.IsMarked()); EXPECT_TRUE(header2.TryMarkAtomic()); EXPECT_TRUE(header2.IsMarked()); - header2.Unmark<HeapObjectHeader::AccessMode::kAtomic>(); + header2.Unmark<AccessMode::kAtomic>(); // GCInfoIndex shares the same bitfield and should be unaffected by Unmark. EXPECT_EQ(kGCInfoIndex, header2.GetGCInfoIndex()); EXPECT_FALSE(header2.IsMarked()); @@ -130,7 +126,7 @@ class ConcurrentGCThread final : public v8::base::Thread { payload_(payload) {} void Run() final { - while (header_->IsInConstruction<HeapObjectHeader::AccessMode::kAtomic>()) { + while (header_->IsInConstruction<AccessMode::kAtomic>()) { } USE(v8::base::AsAtomicPtr(const_cast<size_t*>(&payload_->value)) ->load(std::memory_order_relaxed)); diff --git a/deps/v8/test/unittests/heap/cppgc/heap-unittest.cc b/deps/v8/test/unittests/heap/cppgc/heap-unittest.cc index 0357b24ec0..694d031dda 100644 --- a/deps/v8/test/unittests/heap/cppgc/heap-unittest.cc +++ b/deps/v8/test/unittests/heap/cppgc/heap-unittest.cc @@ -117,5 +117,41 @@ TEST_F(GCHeapTest, ObjectPayloadSize) { EXPECT_LE(expected_size, Heap::From(GetHeap())->ObjectPayloadSize()); } +TEST_F(GCHeapTest, AllocateWithAdditionalBytes) { + static constexpr size_t kBaseSize = sizeof(HeapObjectHeader) + sizeof(Foo); + static constexpr size_t kAdditionalBytes = 10u * kAllocationGranularity; + { + Foo* object = MakeGarbageCollected<Foo>(GetAllocationHandle()); + EXPECT_LE(kBaseSize, HeapObjectHeader::FromPayload(object).GetSize()); + } + { + Foo* object = MakeGarbageCollected<Foo>(GetAllocationHandle(), + AdditionalBytes(kAdditionalBytes)); + EXPECT_LE(kBaseSize + kAdditionalBytes, + HeapObjectHeader::FromPayload(object).GetSize()); + } + { + Foo* object = MakeGarbageCollected<Foo>( + GetAllocationHandle(), + AdditionalBytes(kAdditionalBytes * kAdditionalBytes)); + EXPECT_LE(kBaseSize + kAdditionalBytes * kAdditionalBytes, + HeapObjectHeader::FromPayload(object).GetSize()); + } +} + +TEST_F(GCHeapTest, AllocatedSizeDependOnAdditionalBytes) { + static constexpr size_t kAdditionalBytes = 10u * kAllocationGranularity; + Foo* object = MakeGarbageCollected<Foo>(GetAllocationHandle()); + Foo* object_with_bytes = MakeGarbageCollected<Foo>( + GetAllocationHandle(), AdditionalBytes(kAdditionalBytes)); + Foo* object_with_more_bytes = MakeGarbageCollected<Foo>( + GetAllocationHandle(), + AdditionalBytes(kAdditionalBytes * kAdditionalBytes)); + EXPECT_LT(HeapObjectHeader::FromPayload(object).GetSize(), + HeapObjectHeader::FromPayload(object_with_bytes).GetSize()); + EXPECT_LT(HeapObjectHeader::FromPayload(object_with_bytes).GetSize(), + HeapObjectHeader::FromPayload(object_with_more_bytes).GetSize()); +} + } // namespace internal } // namespace cppgc diff --git a/deps/v8/test/unittests/heap/cppgc/marker-unittest.cc b/deps/v8/test/unittests/heap/cppgc/marker-unittest.cc index b879d9b989..2574db151f 100644 --- a/deps/v8/test/unittests/heap/cppgc/marker-unittest.cc +++ b/deps/v8/test/unittests/heap/cppgc/marker-unittest.cc @@ -28,7 +28,6 @@ class MarkerTest : public testing::TestWithHeap { 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(); @@ -234,7 +233,7 @@ TEST_F(MarkerTest, InConstructionObjectIsEventuallyMarkedEmptyStack) { Member<GCedWithCallback> member(obj); marker->VisitorForTesting().Trace(member); }); - EXPECT_TRUE(HeapObjectHeader::FromPayload(object).IsMarked()); + EXPECT_FALSE(HeapObjectHeader::FromPayload(object).IsMarked()); marker()->FinishMarking(MarkingConfig::StackState::kMayContainHeapPointers); EXPECT_TRUE(HeapObjectHeader::FromPayload(object).IsMarked()); } @@ -248,7 +247,7 @@ TEST_F(MarkerTest, InConstructionObjectIsEventuallyMarkedNonEmptyStack) { GetAllocationHandle(), [marker = marker()](GCedWithCallback* obj) { Member<GCedWithCallback> member(obj); marker->VisitorForTesting().Trace(member); - EXPECT_TRUE(HeapObjectHeader::FromPayload(obj).IsMarked()); + EXPECT_FALSE(HeapObjectHeader::FromPayload(obj).IsMarked()); marker->FinishMarking( MarkingConfig::StackState::kMayContainHeapPointers); EXPECT_TRUE(HeapObjectHeader::FromPayload(obj).IsMarked()); @@ -258,14 +257,20 @@ TEST_F(MarkerTest, InConstructionObjectIsEventuallyMarkedNonEmptyStack) { TEST_F(MarkerTest, SentinelNotClearedOnWeakPersistentHandling) { static const Marker::MarkingConfig config = { MarkingConfig::CollectionType::kMajor, - MarkingConfig::StackState::kNoHeapPointers}; - InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get(), config); + MarkingConfig::StackState::kNoHeapPointers, + MarkingConfig::MarkingType::kIncremental}; Persistent<GCed> root = MakeGarbageCollected<GCed>(GetAllocationHandle()); auto* tmp = MakeGarbageCollected<GCed>(GetAllocationHandle()); root->SetWeakChild(tmp); - marker()->FinishMarking(MarkingConfig::StackState::kNoHeapPointers); + InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get(), config); + while (!marker()->IncrementalMarkingStepForTesting( + MarkingConfig::StackState::kNoHeapPointers)) { + } + // {root} object must be marked at this point because we do not allow + // encountering kSentinelPointer in WeakMember on regular Trace() calls. + ASSERT_TRUE(HeapObjectHeader::FromPayload(root.Get()).IsMarked()); root->SetWeakChild(kSentinelPointer); - marker()->ProcessWeakness(); + marker()->FinishMarking(MarkingConfig::StackState::kNoHeapPointers); EXPECT_EQ(kSentinelPointer, root->weak_child()); } @@ -290,7 +295,6 @@ class IncrementalMarkingTest : public testing::TestWithHeap { 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(); @@ -372,7 +376,7 @@ TEST_F(IncrementalMarkingTest, IncrementalStepDuringAllocation) { holder->member_ = obj; EXPECT_FALSE(header->IsMarked()); FinishSteps(MarkingConfig::StackState::kMayContainHeapPointers); - EXPECT_TRUE(header->IsMarked()); + EXPECT_FALSE(header->IsMarked()); }); FinishSteps(MarkingConfig::StackState::kNoHeapPointers); EXPECT_TRUE(header->IsMarked()); diff --git a/deps/v8/test/unittests/heap/cppgc/marking-verifier-unittest.cc b/deps/v8/test/unittests/heap/cppgc/marking-verifier-unittest.cc index fb5ba772da..603a47399b 100644 --- a/deps/v8/test/unittests/heap/cppgc/marking-verifier-unittest.cc +++ b/deps/v8/test/unittests/heap/cppgc/marking-verifier-unittest.cc @@ -23,7 +23,8 @@ class MarkingVerifierTest : public testing::TestWithHeap { void VerifyMarking(HeapBase& heap, StackState stack_state) { Heap::From(GetHeap())->object_allocator().ResetLinearAllocationBuffers(); - MarkingVerifier verifier(heap, stack_state); + MarkingVerifier verifier(heap); + verifier.Run(stack_state); } }; @@ -98,6 +99,48 @@ TEST_F(MarkingVerifierTest, DoesntDieOnInConstructionOnObject) { }); } +namespace { +class GCedWithCallbackAndChild final + : public GarbageCollected<GCedWithCallbackAndChild> { + public: + template <typename Callback> + GCedWithCallbackAndChild(GCed* gced, Callback callback) : child_(gced) { + callback(this); + } + void Trace(cppgc::Visitor* visitor) const { visitor->Trace(child_); } + + private: + Member<GCed> child_; +}; + +template <typename T> +struct Holder : public GarbageCollected<Holder<T>> { + public: + void Trace(cppgc::Visitor* visitor) const { visitor->Trace(object); } + Member<T> object = nullptr; +}; +} // namespace + +TEST_F(MarkingVerifierTest, DoesntDieOnInConstructionObjectWithWriteBarrier) { + // Regression test: https://crbug.com/v8/10989. + // GCedWithCallbackAndChild is marked by write barrier and then discarded by + // FlushNotFullyConstructedObjects because it is already marked. + Persistent<Holder<GCedWithCallbackAndChild>> persistent = + MakeGarbageCollected<Holder<GCedWithCallbackAndChild>>( + GetAllocationHandle()); + GarbageCollector::Config config = + GarbageCollector::Config::PreciseIncrementalConfig(); + Heap::From(GetHeap())->StartIncrementalGarbageCollection(config); + MakeGarbageCollected<GCedWithCallbackAndChild>( + GetAllocationHandle(), MakeGarbageCollected<GCed>(GetAllocationHandle()), + [&persistent](GCedWithCallbackAndChild* obj) { + persistent->object = obj; + }); + GetMarkerRef()->IncrementalMarkingStepForTesting( + GarbageCollector::Config::StackState::kNoHeapPointers); + Heap::From(GetHeap())->FinalizeIncrementalGarbageCollectionIfRunning(config); +} + // Death tests. namespace { diff --git a/deps/v8/test/unittests/heap/cppgc/marking-visitor-unittest.cc b/deps/v8/test/unittests/heap/cppgc/marking-visitor-unittest.cc index 51387712c7..a411c7e63a 100644 --- a/deps/v8/test/unittests/heap/cppgc/marking-visitor-unittest.cc +++ b/deps/v8/test/unittests/heap/cppgc/marking-visitor-unittest.cc @@ -44,17 +44,20 @@ class GCedWithMixin : public GarbageCollected<GCedWithMixin>, public Mixin { void Trace(cppgc::Visitor*) const override {} }; -class TestMarkingVisitor : public MarkingVisitor { +class TestMarkingVisitor : public MutatorMarkingVisitor { public: explicit TestMarkingVisitor(Marker* marker) - : MarkingVisitor(marker->heap(), marker->MarkingStateForTesting()) {} + : MutatorMarkingVisitor(marker->heap(), + marker->MutatorMarkingStateForTesting()) {} ~TestMarkingVisitor() { marking_state_.Publish(); } + + MarkingStateBase& marking_state() { return marking_state_; } }; } // namespace TEST_F(MarkingVisitorTest, MarkedBytesAreInitiallyZero) { - EXPECT_EQ(0u, GetMarker()->MarkingStateForTesting().marked_bytes()); + EXPECT_EQ(0u, GetMarker()->MutatorMarkingStateForTesting().marked_bytes()); } // Strong references are marked. @@ -216,7 +219,10 @@ TEST_F(MarkingVisitorTest, MarkMemberInConstruction) { Member<GCedWithInConstructionCallback> object(obj); visitor.Trace(object); }); - EXPECT_TRUE(HeapObjectHeader::FromPayload(gced).IsMarked()); + HeapObjectHeader& header = HeapObjectHeader::FromPayload(gced); + EXPECT_TRUE(visitor.marking_state().not_fully_constructed_worklist().Contains( + &header)); + EXPECT_FALSE(header.IsMarked()); } TEST_F(MarkingVisitorTest, MarkMemberMixinInConstruction) { @@ -228,7 +234,10 @@ TEST_F(MarkingVisitorTest, MarkMemberMixinInConstruction) { Member<MixinWithInConstructionCallback> mixin(obj); visitor.Trace(mixin); }); - EXPECT_TRUE(HeapObjectHeader::FromPayload(gced).IsMarked()); + HeapObjectHeader& header = HeapObjectHeader::FromPayload(gced); + EXPECT_TRUE(visitor.marking_state().not_fully_constructed_worklist().Contains( + &header)); + EXPECT_FALSE(header.IsMarked()); } TEST_F(MarkingVisitorTest, DontMarkWeakMemberInConstruction) { @@ -240,7 +249,11 @@ TEST_F(MarkingVisitorTest, DontMarkWeakMemberInConstruction) { WeakMember<GCedWithInConstructionCallback> object(obj); visitor.Trace(object); }); - EXPECT_FALSE(HeapObjectHeader::FromPayload(gced).IsMarked()); + HeapObjectHeader& header = HeapObjectHeader::FromPayload(gced); + EXPECT_FALSE( + visitor.marking_state().not_fully_constructed_worklist().Contains( + &header)); + EXPECT_FALSE(header.IsMarked()); } TEST_F(MarkingVisitorTest, DontMarkWeakMemberMixinInConstruction) { @@ -252,7 +265,11 @@ TEST_F(MarkingVisitorTest, DontMarkWeakMemberMixinInConstruction) { WeakMember<MixinWithInConstructionCallback> mixin(obj); visitor.Trace(mixin); }); - EXPECT_FALSE(HeapObjectHeader::FromPayload(gced).IsMarked()); + HeapObjectHeader& header = HeapObjectHeader::FromPayload(gced); + EXPECT_FALSE( + visitor.marking_state().not_fully_constructed_worklist().Contains( + &header)); + EXPECT_FALSE(header.IsMarked()); } TEST_F(MarkingVisitorTest, MarkPersistentInConstruction) { @@ -264,7 +281,10 @@ TEST_F(MarkingVisitorTest, MarkPersistentInConstruction) { Persistent<GCedWithInConstructionCallback> object(obj); visitor.TraceRootForTesting(object, SourceLocation::Current()); }); - EXPECT_TRUE(HeapObjectHeader::FromPayload(gced).IsMarked()); + HeapObjectHeader& header = HeapObjectHeader::FromPayload(gced); + EXPECT_TRUE(visitor.marking_state().not_fully_constructed_worklist().Contains( + &header)); + EXPECT_FALSE(header.IsMarked()); } TEST_F(MarkingVisitorTest, MarkPersistentMixinInConstruction) { @@ -276,7 +296,23 @@ TEST_F(MarkingVisitorTest, MarkPersistentMixinInConstruction) { Persistent<MixinWithInConstructionCallback> mixin(obj); visitor.TraceRootForTesting(mixin, SourceLocation::Current()); }); - EXPECT_TRUE(HeapObjectHeader::FromPayload(gced).IsMarked()); + HeapObjectHeader& header = HeapObjectHeader::FromPayload(gced); + EXPECT_TRUE(visitor.marking_state().not_fully_constructed_worklist().Contains( + &header)); + EXPECT_FALSE(header.IsMarked()); +} + +TEST_F(MarkingVisitorTest, StrongTracingMarksWeakMember) { + WeakMember<GCed> object(MakeGarbageCollected<GCed>(GetAllocationHandle())); + HeapObjectHeader& header = HeapObjectHeader::FromPayload(object); + + TestMarkingVisitor visitor(GetMarker()); + + EXPECT_FALSE(header.IsMarked()); + + visitor.TraceStrongly(object); + + EXPECT_TRUE(header.IsMarked()); } } // namespace internal diff --git a/deps/v8/test/unittests/heap/cppgc/name-trait-unittest.cc b/deps/v8/test/unittests/heap/cppgc/name-trait-unittest.cc new file mode 100644 index 0000000000..57f100379d --- /dev/null +++ b/deps/v8/test/unittests/heap/cppgc/name-trait-unittest.cc @@ -0,0 +1,133 @@ +// 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/name-trait.h" + +#include "include/cppgc/allocation.h" +#include "include/cppgc/garbage-collected.h" +#include "src/base/build_config.h" +#include "test/unittests/heap/cppgc/tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace cppgc { +namespace internal { + +namespace { + +struct NoName : public GarbageCollected<NoName> { + virtual void Trace(Visitor*) const {} +}; + +struct OtherNoName : public GarbageCollected<OtherNoName> { + virtual void Trace(Visitor*) const {} +}; + +class ClassWithName final : public GarbageCollected<OtherNoName>, + public NameProvider { + public: + explicit ClassWithName(const char* name) : name_(name) {} + virtual void Trace(Visitor*) const {} + const char* GetName() const final { return name_; } + + private: + const char* name_; +}; + +} // namespace + +TEST(NameTraitTest, InternalNamesHiddenInOfficialBuild) { + // Use a runtime test instead of static_assert to allow local builds but block + // enabling the feature accidentally through the waterfall. + // + // Do not include such type information in official builds to + // (a) save binary size on string literals, and + // (b) avoid exposing internal types until it has been clarified whether + // exposing internals in DevTools is fine. +#if defined(OFFICIAL_BUILD) + EXPECT_TRUE(NameProvider::HideInternalNames()); +#endif +} + +TEST(NameTraitTest, DefaultName) { + EXPECT_STREQ(NameProvider::HideInternalNames() + ? "InternalNode" + : "cppgc::internal::(anonymous namespace)::NoName", + NameTrait<NoName>::GetName(nullptr).value); + EXPECT_STREQ(NameProvider::HideInternalNames() + ? "InternalNode" + : "cppgc::internal::(anonymous namespace)::OtherNoName", + NameTrait<OtherNoName>::GetName(nullptr).value); +} + +TEST(NameTraitTest, CustomName) { + ClassWithName with_name("CustomName"); + const char* name = NameTrait<ClassWithName>::GetName(&with_name).value; + EXPECT_STREQ("CustomName", name); +} + +namespace { + +class TraitTester : public NameTraitBase { + public: + // Expose type signature parser to allow testing various inputs. + using NameTraitBase::GetNameFromTypeSignature; +}; + +} // namespace + +TEST(NameTraitTest, NoTypeAvailable) { + HeapObjectName name = TraitTester::GetNameFromTypeSignature(nullptr); + EXPECT_STREQ(NameProvider::kNoNameDeducible, name.value); + EXPECT_TRUE(name.name_was_hidden); +} + +TEST(NameTraitTest, ParsingPrettyFunction) { + // Test assumes that __PRETTY_FUNCTION__ and friends return a string + // containing the the type as [T = <type>]. + HeapObjectName name = TraitTester::GetNameFromTypeSignature( + "Some signature of a method [T = ClassNameInSignature]"); + EXPECT_STREQ("ClassNameInSignature", name.value); + EXPECT_FALSE(name.name_was_hidden); + // While object names are generally leaky, the test needs to be cleaned up + // gracefully. + delete[] name.value; +} + +class HeapObjectHeaderNameTest : public testing::TestWithHeap {}; + +TEST_F(HeapObjectHeaderNameTest, LookupNameThroughGCInfo) { + auto* no_name = MakeGarbageCollected<NoName>(GetAllocationHandle()); + auto no_name_tuple = HeapObjectHeader::FromPayload(no_name).GetName(); + if (NameProvider::HideInternalNames()) { + EXPECT_STREQ(NameProvider::kHiddenName, no_name_tuple.value); + EXPECT_TRUE(no_name_tuple.name_was_hidden); + } else { + EXPECT_STREQ("cppgc::internal::(anonymous namespace)::NoName", + no_name_tuple.value); + EXPECT_FALSE(no_name_tuple.name_was_hidden); + } + + auto* other_no_name = + MakeGarbageCollected<OtherNoName>(GetAllocationHandle()); + auto other_no_name_tuple = + HeapObjectHeader::FromPayload(other_no_name).GetName(); + if (NameProvider::HideInternalNames()) { + EXPECT_STREQ(NameProvider::kHiddenName, no_name_tuple.value); + EXPECT_TRUE(no_name_tuple.name_was_hidden); + } else { + EXPECT_STREQ("cppgc::internal::(anonymous namespace)::OtherNoName", + other_no_name_tuple.value); + EXPECT_FALSE(other_no_name_tuple.name_was_hidden); + } + + auto* class_with_name = + MakeGarbageCollected<ClassWithName>(GetAllocationHandle(), "CustomName"); + auto class_with_name_tuple = + HeapObjectHeader::FromPayload(class_with_name).GetName(); + EXPECT_STREQ("CustomName", class_with_name_tuple.value); + EXPECT_FALSE(class_with_name_tuple.name_was_hidden); +} + +} // namespace internal +} // namespace cppgc diff --git a/deps/v8/test/unittests/heap/cppgc/page-memory-unittest.cc b/deps/v8/test/unittests/heap/cppgc/page-memory-unittest.cc index 6781dacd3d..6c8533c2f0 100644 --- a/deps/v8/test/unittests/heap/cppgc/page-memory-unittest.cc +++ b/deps/v8/test/unittests/heap/cppgc/page-memory-unittest.cc @@ -117,9 +117,16 @@ TEST(PageMemoryRegionTest, PlatformUsesGuardPages) { v8::base::PageAllocator allocator; #if defined(V8_HOST_ARCH_PPC64) && !defined(_AIX) EXPECT_FALSE(SupportsCommittingGuardPages(&allocator)); -#else // !V8_HOST_ARCH_PPC64 +#elif defined(V8_HOST_ARCH_ARM64) + if (allocator.CommitPageSize() == 4096) { + EXPECT_TRUE(SupportsCommittingGuardPages(&allocator)); + } else { + // Arm64 supports both 16k and 64k OS pages. + EXPECT_FALSE(SupportsCommittingGuardPages(&allocator)); + } +#else // Regular case. EXPECT_TRUE(SupportsCommittingGuardPages(&allocator)); -#endif // !V8_HOST_ARCH_PPC64 +#endif } namespace { diff --git a/deps/v8/test/unittests/heap/cppgc/persistent-unittest.cc b/deps/v8/test/unittests/heap/cppgc/persistent-family-unittest.cc index b832bc9d23..ae6ee23625 100644 --- a/deps/v8/test/unittests/heap/cppgc/persistent-unittest.cc +++ b/deps/v8/test/unittests/heap/cppgc/persistent-family-unittest.cc @@ -2,14 +2,15 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "include/cppgc/persistent.h" - #include <vector> #include "include/cppgc/allocation.h" +#include "include/cppgc/cross-thread-persistent.h" #include "include/cppgc/garbage-collected.h" #include "include/cppgc/internal/pointer-policies.h" #include "include/cppgc/member.h" +#include "include/cppgc/persistent.h" +#include "include/cppgc/source-location.h" #include "include/cppgc/type-traits.h" #include "src/heap/cppgc/heap.h" #include "src/heap/cppgc/liveness-broker.h" @@ -33,11 +34,39 @@ struct DerivedGCed : GCed { }; template <template <typename> class PersistentType> +struct PersistentRegionTrait; + +template <> +struct PersistentRegionTrait<Persistent> { + static PersistentRegion& Get(cppgc::Heap* heap) { + return internal::Heap::From(heap)->GetStrongPersistentRegion(); + } +}; + +template <> +struct PersistentRegionTrait<WeakPersistent> { + static PersistentRegion& Get(cppgc::Heap* heap) { + return internal::Heap::From(heap)->GetWeakPersistentRegion(); + } +}; + +template <> +struct PersistentRegionTrait<subtle::CrossThreadPersistent> { + static PersistentRegion& Get(cppgc::Heap* heap) { + return internal::Heap::From(heap)->GetStrongCrossThreadPersistentRegion(); + } +}; + +template <> +struct PersistentRegionTrait<subtle::WeakCrossThreadPersistent> { + static PersistentRegion& Get(cppgc::Heap* heap) { + return internal::Heap::From(heap)->GetWeakCrossThreadPersistentRegion(); + } +}; + +template <template <typename> class PersistentType> PersistentRegion& GetRegion(cppgc::Heap* heap) { - auto* heap_impl = internal::Heap::From(heap); - return IsWeak<PersistentType<GCed>>::value - ? heap_impl->GetWeakPersistentRegion() - : heap_impl->GetStrongPersistentRegion(); + return PersistentRegionTrait<PersistentType>::Get(heap); } template <typename T> @@ -46,6 +75,11 @@ using LocalizedPersistent = internal::KeepLocationPolicy, internal::DefaultCheckingPolicy>; +template <typename T> +using LocalizedCrossThreadPersistent = internal::BasicCrossThreadPersistent< + T, internal::StrongCrossThreadPersistentPolicy, + internal::KeepLocationPolicy, internal::DefaultCheckingPolicy>; + class RootVisitor final : public VisitorBase { public: RootVisitor() = default; @@ -61,11 +95,12 @@ class RootVisitor final : public VisitorBase { } protected: - void VisitRoot(const void* t, TraceDescriptor desc) final { + void VisitRoot(const void* t, TraceDescriptor desc, + const SourceLocation&) final { desc.callback(this, desc.base_object_payload); } void VisitWeakRoot(const void*, TraceDescriptor, WeakCallback callback, - const void* object) final { + const void* object, const SourceLocation&) final { weak_callbacks_.emplace_back(callback, object); } @@ -112,6 +147,8 @@ TEST_F(PersistentTest, NullStateCtor) { auto* heap = GetHeap(); NullStateCtor<Persistent>(heap); NullStateCtor<WeakPersistent>(heap); + NullStateCtor<subtle::CrossThreadPersistent>(heap); + NullStateCtor<subtle::WeakCrossThreadPersistent>(heap); } template <template <typename> class PersistentType> @@ -136,6 +173,8 @@ TEST_F(PersistentTest, RawCtor) { auto* heap = GetHeap(); RawCtor<Persistent>(heap); RawCtor<WeakPersistent>(heap); + RawCtor<subtle::CrossThreadPersistent>(heap); + RawCtor<subtle::WeakCrossThreadPersistent>(heap); } template <template <typename> class PersistentType> @@ -191,6 +230,8 @@ TEST_F(PersistentTest, CopyCtor) { auto* heap = GetHeap(); CopyCtor<Persistent>(heap); CopyCtor<WeakPersistent>(heap); + CopyCtor<subtle::CrossThreadPersistent>(heap); + CopyCtor<subtle::WeakCrossThreadPersistent>(heap); } template <template <typename> class PersistentType> @@ -233,6 +274,8 @@ TEST_F(PersistentTest, MoveCtor) { auto* heap = GetHeap(); MoveCtor<Persistent>(heap); MoveCtor<WeakPersistent>(heap); + MoveCtor<subtle::CrossThreadPersistent>(heap); + MoveCtor<subtle::WeakCrossThreadPersistent>(heap); } template <template <typename> class PersistentType, @@ -258,10 +301,16 @@ TEST_F(PersistentTest, MemberCtor) { MemberCtor<WeakPersistent, Member>(heap); MemberCtor<WeakPersistent, WeakMember>(heap); MemberCtor<WeakPersistent, UntracedMember>(heap); + MemberCtor<subtle::CrossThreadPersistent, Member>(heap); + MemberCtor<subtle::CrossThreadPersistent, WeakMember>(heap); + MemberCtor<subtle::CrossThreadPersistent, UntracedMember>(heap); + MemberCtor<subtle::WeakCrossThreadPersistent, Member>(heap); + MemberCtor<subtle::WeakCrossThreadPersistent, WeakMember>(heap); + MemberCtor<subtle::WeakCrossThreadPersistent, UntracedMember>(heap); } template <template <typename> class PersistentType> -void NullStateAssignemnt(cppgc::Heap* heap) { +void NullStateAssignment(cppgc::Heap* heap) { EXPECT_EQ(0u, GetRegion<PersistentType>(heap).NodesInUse()); { PersistentType<GCed> p = @@ -292,8 +341,10 @@ void NullStateAssignemnt(cppgc::Heap* heap) { TEST_F(PersistentTest, NullStateAssignemnt) { auto* heap = GetHeap(); - NullStateAssignemnt<Persistent>(heap); - NullStateAssignemnt<WeakPersistent>(heap); + NullStateAssignment<Persistent>(heap); + NullStateAssignment<WeakPersistent>(heap); + NullStateAssignment<subtle::CrossThreadPersistent>(heap); + NullStateAssignment<subtle::WeakCrossThreadPersistent>(heap); } template <template <typename> class PersistentType> @@ -322,6 +373,8 @@ TEST_F(PersistentTest, RawAssignment) { auto* heap = GetHeap(); RawAssignment<Persistent>(heap); RawAssignment<WeakPersistent>(heap); + RawAssignment<subtle::CrossThreadPersistent>(heap); + RawAssignment<subtle::WeakCrossThreadPersistent>(heap); } template <template <typename> class PersistentType> @@ -384,6 +437,8 @@ TEST_F(PersistentTest, CopyAssignment) { auto* heap = GetHeap(); CopyAssignment<Persistent>(heap); CopyAssignment<WeakPersistent>(heap); + CopyAssignment<subtle::CrossThreadPersistent>(heap); + CopyAssignment<subtle::WeakCrossThreadPersistent>(heap); } template <template <typename> class PersistentType> @@ -444,6 +499,8 @@ TEST_F(PersistentTest, MoveAssignment) { auto* heap = GetHeap(); MoveAssignment<Persistent>(heap); MoveAssignment<WeakPersistent>(heap); + MoveAssignment<subtle::CrossThreadPersistent>(heap); + MoveAssignment<subtle::WeakCrossThreadPersistent>(heap); } template <template <typename> class PersistentType, @@ -470,6 +527,12 @@ TEST_F(PersistentTest, MemberAssignment) { MemberAssignment<WeakPersistent, Member>(heap); MemberAssignment<WeakPersistent, WeakMember>(heap); MemberAssignment<WeakPersistent, UntracedMember>(heap); + MemberAssignment<subtle::CrossThreadPersistent, Member>(heap); + MemberAssignment<subtle::CrossThreadPersistent, WeakMember>(heap); + MemberAssignment<subtle::CrossThreadPersistent, UntracedMember>(heap); + MemberAssignment<subtle::WeakCrossThreadPersistent, Member>(heap); + MemberAssignment<subtle::WeakCrossThreadPersistent, WeakMember>(heap); + MemberAssignment<subtle::WeakCrossThreadPersistent, UntracedMember>(heap); } template <template <typename> class PersistentType> @@ -488,6 +551,8 @@ TEST_F(PersistentTest, Clear) { auto* heap = GetHeap(); ClearTest<Persistent>(heap); ClearTest<WeakPersistent>(heap); + ClearTest<subtle::CrossThreadPersistent>(heap); + ClearTest<subtle::WeakCrossThreadPersistent>(heap); } template <template <typename> class PersistentType> @@ -507,6 +572,8 @@ TEST_F(PersistentTest, Release) { auto* heap = GetHeap(); ReleaseTest<Persistent>(heap); ReleaseTest<WeakPersistent>(heap); + ReleaseTest<subtle::CrossThreadPersistent>(heap); + ReleaseTest<subtle::WeakCrossThreadPersistent>(heap); } template <template <typename> class PersistentType1, @@ -630,6 +697,10 @@ TEST_F(PersistentTest, ClearOnHeapDestruction) { weak_persistent = MakeGarbageCollected<GCed>(heap->GetAllocationHandle()); const Persistent<GCed> persistent_sentinel(kSentinelPointer); const WeakPersistent<GCed> weak_persistent_sentinel(kSentinelPointer); + const subtle::CrossThreadPersistent<GCed> cross_thread_persistent_sentinel( + kSentinelPointer); + const subtle::WeakCrossThreadPersistent<GCed> + cross_thread_weak_persistent_sentinel(kSentinelPointer); heap.reset(); EXPECT_EQ(nullptr, persistent); @@ -651,6 +722,14 @@ TEST_F(PersistentTest, LocalizedPersistent) { EXPECT_EQ(expected_loc.Line() + 1, actual_loc.Line()); } { + const auto expected_loc = SourceLocation::Current(); + LocalizedCrossThreadPersistent<GCed> p = gced; + const auto actual_loc = p.Location(); + EXPECT_STREQ(expected_loc.Function(), actual_loc.Function()); + EXPECT_STREQ(expected_loc.FileName(), actual_loc.FileName()); + EXPECT_EQ(expected_loc.Line() + 1, actual_loc.Line()); + } + { // Copy ctor doesn't copy source location. LocalizedPersistent<GCed> p1 = gced; LocalizedPersistent<GCed> p2 = p1; @@ -659,6 +738,14 @@ TEST_F(PersistentTest, LocalizedPersistent) { EXPECT_EQ(p1.Location().Line() + 1, p2.Location().Line()); } { + // Copy ctor doesn't copy source location. + LocalizedCrossThreadPersistent<GCed> p1 = gced; + LocalizedCrossThreadPersistent<GCed> p2 = p1; + EXPECT_STREQ(p1.Location().Function(), p2.Location().Function()); + EXPECT_STREQ(p1.Location().FileName(), p2.Location().FileName()); + EXPECT_EQ(p1.Location().Line() + 1, p2.Location().Line()); + } + { // Copy assignment doesn't copy source location. LocalizedPersistent<GCed> p1 = gced; LocalizedPersistent<GCed> p2; @@ -668,6 +755,15 @@ TEST_F(PersistentTest, LocalizedPersistent) { EXPECT_EQ(p1.Location().Line() + 1, p2.Location().Line()); } { + // Copy assignment doesn't copy source location. + LocalizedCrossThreadPersistent<GCed> p1 = gced; + LocalizedCrossThreadPersistent<GCed> p2; + p2 = p1; + EXPECT_STREQ(p1.Location().Function(), p2.Location().Function()); + EXPECT_STREQ(p1.Location().FileName(), p2.Location().FileName()); + EXPECT_EQ(p1.Location().Line() + 1, p2.Location().Line()); + } + { // Clearing doesn't clear source location. LocalizedPersistent<GCed> p1 = gced; LocalizedPersistent<GCed> p2 = gced; @@ -677,6 +773,15 @@ TEST_F(PersistentTest, LocalizedPersistent) { EXPECT_EQ(p1.Location().Line() + 1, p2.Location().Line()); } { + // Clearing doesn't clear source location. + LocalizedCrossThreadPersistent<GCed> p1 = gced; + LocalizedCrossThreadPersistent<GCed> p2 = gced; + p2.Clear(); + EXPECT_STREQ(p1.Location().Function(), p2.Location().Function()); + EXPECT_STREQ(p1.Location().FileName(), p2.Location().FileName()); + EXPECT_EQ(p1.Location().Line() + 1, p2.Location().Line()); + } + { LocalizedPersistent<GCed> p1 = gced; const auto expected_loc = p1.Location(); LocalizedPersistent<GCed> p2 = std::move(p1); @@ -685,6 +790,14 @@ TEST_F(PersistentTest, LocalizedPersistent) { EXPECT_EQ(expected_loc.Line(), p2.Location().Line()); } { + LocalizedCrossThreadPersistent<GCed> p1 = gced; + const auto expected_loc = p1.Location(); + LocalizedCrossThreadPersistent<GCed> p2 = std::move(p1); + EXPECT_STREQ(expected_loc.Function(), p2.Location().Function()); + EXPECT_STREQ(expected_loc.FileName(), p2.Location().FileName()); + EXPECT_EQ(expected_loc.Line(), p2.Location().Line()); + } + { LocalizedPersistent<GCed> p1 = gced; const auto expected_loc = p1.Location(); LocalizedPersistent<GCed> p2; @@ -693,8 +806,57 @@ TEST_F(PersistentTest, LocalizedPersistent) { EXPECT_STREQ(expected_loc.FileName(), p2.Location().FileName()); EXPECT_EQ(expected_loc.Line(), p2.Location().Line()); } + { + LocalizedCrossThreadPersistent<GCed> p1 = gced; + const auto expected_loc = p1.Location(); + LocalizedCrossThreadPersistent<GCed> p2; + p2 = std::move(p1); + EXPECT_STREQ(expected_loc.Function(), p2.Location().Function()); + EXPECT_STREQ(expected_loc.FileName(), p2.Location().FileName()); + EXPECT_EQ(expected_loc.Line(), p2.Location().Line()); + } } + #endif +namespace { + +class ExpectingLocationVisitor final : public VisitorBase { + public: + explicit ExpectingLocationVisitor(const SourceLocation& expected_location) + : expected_loc_(expected_location) {} + + protected: + void VisitRoot(const void* t, TraceDescriptor desc, + const SourceLocation& loc) final { + EXPECT_STREQ(expected_loc_.Function(), loc.Function()); + EXPECT_STREQ(expected_loc_.FileName(), loc.FileName()); + EXPECT_EQ(expected_loc_.Line(), loc.Line()); + } + + private: + const SourceLocation& expected_loc_; +}; + +} // namespace + +TEST_F(PersistentTest, PersistentTraceLocation) { + GCed* gced = MakeGarbageCollected<GCed>(GetAllocationHandle()); + { +#if CPPGC_SUPPORTS_SOURCE_LOCATION + // Baseline for creating expected location which has a different line + // number. + const auto loc = SourceLocation::Current(); + const auto expected_loc = + SourceLocation::Current(loc.Function(), loc.FileName(), loc.Line() + 6); +#else // !CCPPGC_SUPPORTS_SOURCE_LOCATION + const SourceLocation expected_loc; +#endif // !CCPPGC_SUPPORTS_SOURCE_LOCATION + LocalizedPersistent<GCed> p = gced; + ExpectingLocationVisitor visitor(expected_loc); + visitor.TraceRootForTesting(p, p.Location()); + } +} + } // namespace internal } // namespace cppgc diff --git a/deps/v8/test/unittests/heap/cppgc/stack-unittest.cc b/deps/v8/test/unittests/heap/cppgc/stack-unittest.cc index a4b50f8d30..0fff908b36 100644 --- a/deps/v8/test/unittests/heap/cppgc/stack-unittest.cc +++ b/deps/v8/test/unittests/heap/cppgc/stack-unittest.cc @@ -261,6 +261,8 @@ TEST_F(GCStackTest, IteratePointersFindsParameterNesting7) { EXPECT_TRUE(scanner->found()); } +// Disabled on msvc, due to miscompilation, see https://crbug.com/v8/10658. +#if !defined(_MSC_VER) || defined(__clang__) TEST_F(GCStackTest, IteratePointersFindsParameterNesting8) { auto scanner = std::make_unique<StackScanner>(); void* needle = RecursivelyPassOnParameter(8, scanner->needle(), GetStack(), @@ -268,6 +270,7 @@ TEST_F(GCStackTest, IteratePointersFindsParameterNesting8) { EXPECT_EQ(scanner->needle(), needle); EXPECT_TRUE(scanner->found()); } +#endif // !_MSC_VER || __clang__ // The following test uses inline assembly and has been checked to work on clang // to verify that the stack-scanning trampoline pushes callee-saved registers. diff --git a/deps/v8/test/unittests/heap/cppgc/sweeper-unittest.cc b/deps/v8/test/unittests/heap/cppgc/sweeper-unittest.cc index 3591af29a4..8031deac41 100644 --- a/deps/v8/test/unittests/heap/cppgc/sweeper-unittest.cc +++ b/deps/v8/test/unittests/heap/cppgc/sweeper-unittest.cc @@ -48,7 +48,10 @@ class SweeperTest : public testing::TestWithHeap { // methods are called in the right order. heap->stats_collector()->NotifyMarkingStarted(); heap->stats_collector()->NotifyMarkingCompleted(0); - sweeper.Start(Sweeper::Config::kAtomic); + const Sweeper::SweepingConfig sweeping_config{ + Sweeper::SweepingConfig::SweepingType::kAtomic, + Sweeper::SweepingConfig::CompactableSpaceHandling::kSweep}; + sweeper.Start(sweeping_config); sweeper.FinishIfRunning(); } diff --git a/deps/v8/test/unittests/heap/cppgc/test-platform.cc b/deps/v8/test/unittests/heap/cppgc/test-platform.cc index c649b1e89d..2268d546b3 100644 --- a/deps/v8/test/unittests/heap/cppgc/test-platform.cc +++ b/deps/v8/test/unittests/heap/cppgc/test-platform.cc @@ -4,132 +4,29 @@ #include "test/unittests/heap/cppgc/test-platform.h" +#include "include/libplatform/libplatform.h" #include "src/base/platform/platform.h" #include "src/base/platform/time.h" -#include "src/heap/cppgc/default-job.h" namespace cppgc { namespace internal { namespace testing { -namespace { -class TestJobThread final : public v8::base::Thread { - public: - using id = uint8_t; - - explicit TestJobThread(TestJob* job) : Thread(Options("job")), job_(job) {} - - void Run() final; - - static size_t GetMaxSupportedConcurrency() { return 4u; } - - private: - TestJob* const job_; -}; -} // namespace - -// Default implementation of Jobs based on std::thread. -class TestJob final : public DefaultJobImpl<TestJobThread> { - public: - explicit TestJob(Key key, std::unique_ptr<cppgc::JobTask> job_task) - : DefaultJobImpl(key, std::move(job_task)) {} - - std::shared_ptr<TestJobThread> CreateThread(DefaultJobImpl* job) final { - std::shared_ptr<TestJobThread> thread = - std::make_shared<TestJobThread>(this); - const bool thread_started = thread->Start(); - USE(thread_started); - DCHECK(thread_started); - return thread; - } -}; - -void TestJobThread::Run() { - DCHECK_NOT_NULL(job_); - job_->RunJobTask(); -} - -void TestTaskRunner::PostTask(std::unique_ptr<cppgc::Task> task) { - tasks_.push_back(std::move(task)); -} - -void TestTaskRunner::PostNonNestableTask(std::unique_ptr<cppgc::Task> task) { - PostTask(std::move(task)); -} - -void TestTaskRunner::PostDelayedTask(std::unique_ptr<cppgc::Task> task, - double) { - PostTask(std::move(task)); -} - -void TestTaskRunner::PostNonNestableDelayedTask( - std::unique_ptr<cppgc::Task> task, double) { - PostTask(std::move(task)); -} - -void TestTaskRunner::PostIdleTask(std::unique_ptr<cppgc::IdleTask> task) { - idle_tasks_.push_back(std::move(task)); -} - -bool TestTaskRunner::RunSingleTask() { - if (!tasks_.size()) return false; - - tasks_.back()->Run(); - tasks_.pop_back(); - - return true; -} - -bool TestTaskRunner::RunSingleIdleTask(double deadline_in_seconds) { - if (!idle_tasks_.size()) return false; - - idle_tasks_.back()->Run(deadline_in_seconds); - idle_tasks_.pop_back(); - - return true; -} - -void TestTaskRunner::RunUntilIdle() { - for (auto& task : tasks_) { - task->Run(); - } - tasks_.clear(); - - for (auto& task : idle_tasks_) { - task->Run(std::numeric_limits<double>::infinity()); - } - idle_tasks_.clear(); -} - TestPlatform::TestPlatform() - : foreground_task_runner_(std::make_unique<TestTaskRunner>()) {} - -TestPlatform::~TestPlatform() V8_NOEXCEPT { WaitAllBackgroundTasks(); } + : DefaultPlatform(0, DefaultPlatform::IdleTaskSupport::kEnabled) {} std::unique_ptr<cppgc::JobHandle> TestPlatform::PostJob( - cppgc::TaskPriority, std::unique_ptr<cppgc::JobTask> job_task) { - if (AreBackgroundTasksDisabled()) return {}; - - std::shared_ptr<TestJob> job = - DefaultJobFactory<TestJob>::Create(std::move(job_task)); - jobs_.push_back(job); - return std::make_unique<TestJob::JobHandle>(std::move(job)); -} - -double TestPlatform::MonotonicallyIncreasingTime() { - return v8::base::TimeTicks::HighResolutionNow().ToInternalValue() / - static_cast<double>(v8::base::Time::kMicrosecondsPerSecond); -} - -void TestPlatform::WaitAllForegroundTasks() { - foreground_task_runner_->RunUntilIdle(); + cppgc::TaskPriority priority, std::unique_ptr<cppgc::JobTask> job_task) { + if (AreBackgroundTasksDisabled()) return nullptr; + return v8_platform_->PostJob(priority, std::move(job_task)); } -void TestPlatform::WaitAllBackgroundTasks() { - for (auto& job : jobs_) { - job->Join(); +void TestPlatform::RunAllForegroundTasks() { + v8::platform::PumpMessageLoop(v8_platform_.get(), kNoIsolate); + if (GetForegroundTaskRunner()->IdleTasksEnabled()) { + v8::platform::RunIdleTasks(v8_platform_.get(), kNoIsolate, + std::numeric_limits<double>::max()); } - jobs_.clear(); } TestPlatform::DisableBackgroundTasksScope::DisableBackgroundTasksScope( diff --git a/deps/v8/test/unittests/heap/cppgc/test-platform.h b/deps/v8/test/unittests/heap/cppgc/test-platform.h index 1faa6efb40..d6a93f45c9 100644 --- a/deps/v8/test/unittests/heap/cppgc/test-platform.h +++ b/deps/v8/test/unittests/heap/cppgc/test-platform.h @@ -5,45 +5,14 @@ #ifndef V8_UNITTESTS_HEAP_CPPGC_TEST_PLATFORM_H_ #define V8_UNITTESTS_HEAP_CPPGC_TEST_PLATFORM_H_ -#include <memory> -#include <vector> - -#include "include/cppgc/platform.h" -#include "src/base/page-allocator.h" -#include "src/base/platform/platform.h" +#include "include/cppgc/default-platform.h" +#include "src/base/compiler-specific.h" namespace cppgc { namespace internal { namespace testing { -class TestJob; - -class TestTaskRunner : public cppgc::TaskRunner { - public: - void PostTask(std::unique_ptr<cppgc::Task> task) override; - void PostDelayedTask(std::unique_ptr<cppgc::Task> task, double) override; - - bool NonNestableTasksEnabled() const override { return true; } - void PostNonNestableTask(std::unique_ptr<cppgc::Task> task) override; - - bool NonNestableDelayedTasksEnabled() const override { return true; } - void PostNonNestableDelayedTask(std::unique_ptr<cppgc::Task> task, - double) override; - - bool IdleTasksEnabled() override { return true; } - void PostIdleTask(std::unique_ptr<cppgc::IdleTask> task) override; - - bool RunSingleTask(); - bool RunSingleIdleTask(double duration_in_seconds); - - void RunUntilIdle(); - - private: - std::vector<std::unique_ptr<cppgc::Task>> tasks_; - std::vector<std::unique_ptr<cppgc::IdleTask>> idle_tasks_; -}; - -class TestPlatform : public Platform { +class TestPlatform : public DefaultPlatform { public: class DisableBackgroundTasksScope { public: @@ -55,32 +24,18 @@ class TestPlatform : public Platform { }; TestPlatform(); - ~TestPlatform() V8_NOEXCEPT override; - PageAllocator* GetPageAllocator() override { return &page_allocator_; } - - std::shared_ptr<cppgc::TaskRunner> GetForegroundTaskRunner() override { - return foreground_task_runner_; - } - - // TestPlatform does not support job priorities. All jobs would be assigned - // the same priority regardless of the cppgc::TaskPriority parameter. std::unique_ptr<cppgc::JobHandle> PostJob( - cppgc::TaskPriority, std::unique_ptr<cppgc::JobTask> job_task) override; - - double MonotonicallyIncreasingTime() override; + cppgc::TaskPriority priority, + std::unique_ptr<cppgc::JobTask> job_task) final; - void WaitAllForegroundTasks(); - void WaitAllBackgroundTasks(); + void RunAllForegroundTasks(); private: bool AreBackgroundTasksDisabled() const { return disabled_background_tasks_ > 0; } - v8::base::PageAllocator page_allocator_; - std::shared_ptr<TestTaskRunner> foreground_task_runner_; - std::vector<std::shared_ptr<TestJob>> jobs_; size_t disabled_background_tasks_ = 0; }; diff --git a/deps/v8/test/unittests/heap/cppgc/tests.h b/deps/v8/test/unittests/heap/cppgc/tests.h index 175116d985..ac445c3370 100644 --- a/deps/v8/test/unittests/heap/cppgc/tests.h +++ b/deps/v8/test/unittests/heap/cppgc/tests.h @@ -33,8 +33,9 @@ class TestWithHeap : public TestWithPlatform { TestWithHeap(); void PreciseGC() { - heap_->ForceGarbageCollectionSlow("TestWithHeap", "Testing", - cppgc::Heap::StackState::kNoHeapPointers); + heap_->ForceGarbageCollectionSlow( + ::testing::UnitTest::GetInstance()->current_test_info()->name(), + "Testing", cppgc::Heap::StackState::kNoHeapPointers); } cppgc::Heap* GetHeap() const { return heap_.get(); } diff --git a/deps/v8/test/unittests/heap/cppgc/weak-container-unittest.cc b/deps/v8/test/unittests/heap/cppgc/weak-container-unittest.cc new file mode 100644 index 0000000000..d21f4249b3 --- /dev/null +++ b/deps/v8/test/unittests/heap/cppgc/weak-container-unittest.cc @@ -0,0 +1,184 @@ +// 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/allocation.h" +#include "src/heap/cppgc/marker.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 WeakContainerTest : public testing::TestWithHeap { + public: + using Config = Marker::MarkingConfig; + + void StartMarking() { + Config config = {Config::CollectionType::kMajor, + Config::StackState::kNoHeapPointers, + Config::MarkingType::kIncremental}; + GetMarkerRef() = MarkerFactory::CreateAndStartMarking<Marker>( + Heap::From(GetHeap())->AsBase(), GetPlatformHandle().get(), config); + } + + void FinishMarking(Config::StackState stack_state) { + GetMarkerRef()->FinishMarking(stack_state); + Heap::From(GetHeap())->stats_collector()->NotifySweepingCompleted(); + } +}; + +class TraceableGCed : public GarbageCollected<TraceableGCed> { + public: + void Trace(cppgc::Visitor*) const { n_trace_calls++; } + static size_t n_trace_calls; +}; +size_t TraceableGCed::n_trace_calls = 0u; + +class NonTraceableGCed : public GarbageCollected<NonTraceableGCed> { + public: + void Trace(cppgc::Visitor*) const { n_trace_calls++; } + static size_t n_trace_calls; +}; +size_t NonTraceableGCed::n_trace_calls = 0u; + +void EmptyWeakCallback(const LivenessBroker&, const void*) {} + +template <typename T> +V8_NOINLINE T access(volatile const T& t) { + return t; +} + +} // namespace + +} // namespace internal + +template <> +struct TraceTrait<internal::TraceableGCed> + : public internal::TraceTraitBase<internal::TraceableGCed> { + static TraceDescriptor GetWeakTraceDescriptor(const void* self) { + return {self, Trace}; + } +}; + +template <> +struct TraceTrait<internal::NonTraceableGCed> + : public internal::TraceTraitBase<internal::NonTraceableGCed> { + static TraceDescriptor GetWeakTraceDescriptor(const void* self) { + return {self, nullptr}; + } +}; + +namespace internal { + +TEST_F(WeakContainerTest, TraceableGCedTraced) { + TraceableGCed* obj = + MakeGarbageCollected<TraceableGCed>(GetAllocationHandle()); + TraceableGCed::n_trace_calls = 0u; + StartMarking(); + GetMarkerRef()->VisitorForTesting().TraceWeakContainer(obj, EmptyWeakCallback, + nullptr); + FinishMarking(Config::StackState::kNoHeapPointers); + EXPECT_NE(0u, TraceableGCed::n_trace_calls); + access(obj); +} + +TEST_F(WeakContainerTest, NonTraceableGCedNotTraced) { + NonTraceableGCed* obj = + MakeGarbageCollected<NonTraceableGCed>(GetAllocationHandle()); + NonTraceableGCed::n_trace_calls = 0u; + StartMarking(); + GetMarkerRef()->VisitorForTesting().TraceWeakContainer(obj, EmptyWeakCallback, + nullptr); + FinishMarking(Config::StackState::kNoHeapPointers); + EXPECT_EQ(0u, NonTraceableGCed::n_trace_calls); + access(obj); +} + +TEST_F(WeakContainerTest, NonTraceableGCedNotTracedConservatively) { + NonTraceableGCed* obj = + MakeGarbageCollected<NonTraceableGCed>(GetAllocationHandle()); + NonTraceableGCed::n_trace_calls = 0u; + StartMarking(); + GetMarkerRef()->VisitorForTesting().TraceWeakContainer(obj, EmptyWeakCallback, + nullptr); + FinishMarking(Config::StackState::kMayContainHeapPointers); + EXPECT_NE(0u, NonTraceableGCed::n_trace_calls); + access(obj); +} + +TEST_F(WeakContainerTest, ConservativeGCTracesWeakContainer) { + size_t trace_count_without_conservative; + { + TraceableGCed* obj = + MakeGarbageCollected<TraceableGCed>(GetAllocationHandle()); + TraceableGCed::n_trace_calls = 0u; + StartMarking(); + GetMarkerRef()->VisitorForTesting().TraceWeakContainer( + obj, EmptyWeakCallback, nullptr); + FinishMarking(Config::StackState::kNoHeapPointers); + trace_count_without_conservative = TraceableGCed::n_trace_calls; + access(obj); + } + { + TraceableGCed* obj = + MakeGarbageCollected<TraceableGCed>(GetAllocationHandle()); + TraceableGCed::n_trace_calls = 0u; + StartMarking(); + GetMarkerRef()->VisitorForTesting().TraceWeakContainer( + obj, EmptyWeakCallback, nullptr); + FinishMarking(Config::StackState::kMayContainHeapPointers); + EXPECT_LT(trace_count_without_conservative, TraceableGCed::n_trace_calls); + access(obj); + } +} + +TEST_F(WeakContainerTest, ConservativeGCTracesWeakContainerOnce) { + NonTraceableGCed* obj = + MakeGarbageCollected<NonTraceableGCed>(GetAllocationHandle()); + NonTraceableGCed* copy_obj = obj; + USE(copy_obj); + NonTraceableGCed* another_copy_obj = obj; + USE(another_copy_obj); + NonTraceableGCed::n_trace_calls = 0u; + StartMarking(); + GetMarkerRef()->VisitorForTesting().TraceWeakContainer(obj, EmptyWeakCallback, + nullptr); + FinishMarking(Config::StackState::kMayContainHeapPointers); + EXPECT_EQ(1u, NonTraceableGCed::n_trace_calls); + access(obj); +} + +namespace { + +struct WeakCallback { + static void callback(const LivenessBroker&, const void* data) { + n_callback_called++; + obj = data; + } + static size_t n_callback_called; + static const void* obj; +}; +size_t WeakCallback::n_callback_called = 0u; +const void* WeakCallback::obj = nullptr; + +} // namespace + +TEST_F(WeakContainerTest, WeakContainerWeakCallbackCalled) { + TraceableGCed* obj = + MakeGarbageCollected<TraceableGCed>(GetAllocationHandle()); + WeakCallback::n_callback_called = 0u; + WeakCallback::obj = nullptr; + StartMarking(); + GetMarkerRef()->VisitorForTesting().TraceWeakContainer( + obj, WeakCallback::callback, obj); + FinishMarking(Config::StackState::kMayContainHeapPointers); + EXPECT_NE(0u, WeakCallback::n_callback_called); + EXPECT_EQ(obj, WeakCallback::obj); +} + +} // namespace internal +} // namespace cppgc diff --git a/deps/v8/test/unittests/heap/cppgc/write-barrier-unittest.cc b/deps/v8/test/unittests/heap/cppgc/write-barrier-unittest.cc index b06083d1ef..5673d47c8c 100644 --- a/deps/v8/test/unittests/heap/cppgc/write-barrier-unittest.cc +++ b/deps/v8/test/unittests/heap/cppgc/write-barrier-unittest.cc @@ -43,9 +43,10 @@ class ExpectWriteBarrierFires final : private IncrementalMarkingScope { ExpectWriteBarrierFires(MarkerBase* marker, std::initializer_list<void*> objects) : IncrementalMarkingScope(marker), - marking_worklist_(marker->MarkingStateForTesting().marking_worklist()), + marking_worklist_( + marker->MutatorMarkingStateForTesting().marking_worklist()), write_barrier_worklist_( - marker->MarkingStateForTesting().write_barrier_worklist()), + marker->MutatorMarkingStateForTesting().write_barrier_worklist()), objects_(objects) { EXPECT_TRUE(marking_worklist_.IsGlobalEmpty()); EXPECT_TRUE(write_barrier_worklist_.IsGlobalEmpty()); @@ -92,9 +93,10 @@ class ExpectNoWriteBarrierFires final : private IncrementalMarkingScope { ExpectNoWriteBarrierFires(MarkerBase* marker, std::initializer_list<void*> objects) : IncrementalMarkingScope(marker), - marking_worklist_(marker->MarkingStateForTesting().marking_worklist()), + marking_worklist_( + marker->MutatorMarkingStateForTesting().marking_worklist()), write_barrier_worklist_( - marker->MarkingStateForTesting().write_barrier_worklist()) { + marker->MutatorMarkingStateForTesting().write_barrier_worklist()) { EXPECT_TRUE(marking_worklist_.IsGlobalEmpty()); EXPECT_TRUE(write_barrier_worklist_.IsGlobalEmpty()); for (void* object : objects) { |