// 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/heap.h" #include #include #include #include "include/cppgc/allocation.h" #include "include/cppgc/cross-thread-persistent.h" #include "include/cppgc/heap-consistency.h" #include "include/cppgc/heap-state.h" #include "include/cppgc/persistent.h" #include "include/cppgc/prefinalizer.h" #include "src/heap/cppgc/globals.h" #include "test/unittests/heap/cppgc/tests.h" #include "testing/gtest/include/gtest/gtest.h" namespace cppgc { namespace internal { namespace { class GCHeapTest : public testing::TestWithHeap { public: void ConservativeGC() { internal::Heap::From(GetHeap())->CollectGarbage( GCConfig::ConservativeAtomicConfig()); } void PreciseGC() { internal::Heap::From(GetHeap())->CollectGarbage( GCConfig::PreciseAtomicConfig()); } }; class GCHeapDeathTest : public GCHeapTest {}; class Foo : public GarbageCollected { public: static size_t destructor_callcount; Foo() { destructor_callcount = 0; } ~Foo() { destructor_callcount++; } void Trace(cppgc::Visitor*) const {} }; size_t Foo::destructor_callcount; template class GCed : public GarbageCollected> { public: void Trace(cppgc::Visitor*) const {} char buf[Size]; }; } // namespace TEST_F(GCHeapTest, PreciseGCReclaimsObjectOnStack) { Foo* volatile do_not_access = MakeGarbageCollected(GetAllocationHandle()); USE(do_not_access); EXPECT_EQ(0u, Foo::destructor_callcount); PreciseGC(); EXPECT_EQ(1u, Foo::destructor_callcount); PreciseGC(); EXPECT_EQ(1u, Foo::destructor_callcount); } namespace { const void* ConservativeGCReturningObject(cppgc::Heap* heap, const void* object) { internal::Heap::From(heap)->CollectGarbage( GCConfig::ConservativeAtomicConfig()); return object; } } // namespace TEST_F(GCHeapTest, ConservativeGCRetainsObjectOnStack) { Foo* volatile object = MakeGarbageCollected(GetAllocationHandle()); EXPECT_EQ(0u, Foo::destructor_callcount); EXPECT_EQ(object, ConservativeGCReturningObject(GetHeap(), object)); EXPECT_EQ(0u, Foo::destructor_callcount); PreciseGC(); EXPECT_EQ(1u, Foo::destructor_callcount); PreciseGC(); EXPECT_EQ(1u, Foo::destructor_callcount); } namespace { class GCedWithFinalizer final : public GarbageCollected { public: static size_t destructor_counter; GCedWithFinalizer() { destructor_counter = 0; } ~GCedWithFinalizer() { destructor_counter++; } void Trace(Visitor* visitor) const {} }; // static size_t GCedWithFinalizer::destructor_counter = 0; class LargeObjectGCDuringCtor final : public GarbageCollected { public: static constexpr size_t kDataSize = kLargeObjectSizeThreshold + 1; explicit LargeObjectGCDuringCtor(cppgc::Heap* heap) : child_(MakeGarbageCollected( heap->GetAllocationHandle())) { internal::Heap::From(heap)->CollectGarbage( GCConfig::ConservativeAtomicConfig()); } void Trace(Visitor* visitor) const { visitor->Trace(child_); } char data[kDataSize]; Member child_; }; } // namespace TEST_F(GCHeapTest, ConservativeGCFromLargeObjectCtorFindsObject) { GCedWithFinalizer::destructor_counter = 0; MakeGarbageCollected(GetAllocationHandle(), GetHeap()); EXPECT_EQ(0u, GCedWithFinalizer::destructor_counter); } TEST_F(GCHeapTest, ObjectPayloadSize) { static constexpr size_t kNumberOfObjectsPerArena = 16; static constexpr size_t kObjectSizes[] = {1, 32, 64, 128, 2 * kLargeObjectSizeThreshold}; EXPECT_EQ(0u, Heap::From(GetHeap())->ObjectPayloadSize()); { subtle::NoGarbageCollectionScope no_gc(*Heap::From(GetHeap())); for (size_t k = 0; k < kNumberOfObjectsPerArena; ++k) { MakeGarbageCollected>(GetAllocationHandle()); MakeGarbageCollected>(GetAllocationHandle()); MakeGarbageCollected>(GetAllocationHandle()); MakeGarbageCollected>(GetAllocationHandle()); MakeGarbageCollected>(GetAllocationHandle()); } size_t aligned_object_sizes[arraysize(kObjectSizes)]; std::transform(std::cbegin(kObjectSizes), std::cend(kObjectSizes), std::begin(aligned_object_sizes), [](size_t size) { return RoundUp(size, kAllocationGranularity); }); const size_t expected_size = std::accumulate( std::cbegin(aligned_object_sizes), std::cend(aligned_object_sizes), 0u, [](size_t acc, size_t size) { return acc + kNumberOfObjectsPerArena * size; }); // TODO(chromium:1056170): Change to EXPECT_EQ when proper sweeping is // implemented. EXPECT_LE(expected_size, Heap::From(GetHeap())->ObjectPayloadSize()); } PreciseGC(); EXPECT_EQ(0u, 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(GetAllocationHandle()); EXPECT_LE(kBaseSize, HeapObjectHeader::FromObject(object).AllocatedSize()); } { Foo* object = MakeGarbageCollected(GetAllocationHandle(), AdditionalBytes(kAdditionalBytes)); EXPECT_LE(kBaseSize + kAdditionalBytes, HeapObjectHeader::FromObject(object).AllocatedSize()); } { Foo* object = MakeGarbageCollected( GetAllocationHandle(), AdditionalBytes(kAdditionalBytes * kAdditionalBytes)); EXPECT_LE(kBaseSize + kAdditionalBytes * kAdditionalBytes, HeapObjectHeader::FromObject(object).AllocatedSize()); } } TEST_F(GCHeapTest, AllocatedSizeDependOnAdditionalBytes) { static constexpr size_t kAdditionalBytes = 10u * kAllocationGranularity; Foo* object = MakeGarbageCollected(GetAllocationHandle()); Foo* object_with_bytes = MakeGarbageCollected( GetAllocationHandle(), AdditionalBytes(kAdditionalBytes)); Foo* object_with_more_bytes = MakeGarbageCollected( GetAllocationHandle(), AdditionalBytes(kAdditionalBytes * kAdditionalBytes)); EXPECT_LT(HeapObjectHeader::FromObject(object).AllocatedSize(), HeapObjectHeader::FromObject(object_with_bytes).AllocatedSize()); EXPECT_LT( HeapObjectHeader::FromObject(object_with_bytes).AllocatedSize(), HeapObjectHeader::FromObject(object_with_more_bytes).AllocatedSize()); } TEST_F(GCHeapTest, Epoch) { const size_t epoch_before = internal::Heap::From(GetHeap())->epoch(); PreciseGC(); const size_t epoch_after_gc = internal::Heap::From(GetHeap())->epoch(); EXPECT_EQ(epoch_after_gc, epoch_before + 1); } TEST_F(GCHeapTest, NoGarbageCollectionScope) { const size_t epoch_before = internal::Heap::From(GetHeap())->epoch(); { subtle::NoGarbageCollectionScope scope(GetHeap()->GetHeapHandle()); PreciseGC(); } const size_t epoch_after_gc = internal::Heap::From(GetHeap())->epoch(); EXPECT_EQ(epoch_after_gc, epoch_before); } TEST_F(GCHeapTest, IsGarbageCollectionAllowed) { EXPECT_TRUE( subtle::DisallowGarbageCollectionScope::IsGarbageCollectionAllowed( GetHeap()->GetHeapHandle())); { subtle::DisallowGarbageCollectionScope disallow_gc(*Heap::From(GetHeap())); EXPECT_FALSE( subtle::DisallowGarbageCollectionScope::IsGarbageCollectionAllowed( GetHeap()->GetHeapHandle())); } } TEST_F(GCHeapTest, IsMarking) { GCConfig config = GCConfig::PreciseIncrementalMarkingConcurrentSweepingConfig(); auto* heap = Heap::From(GetHeap()); EXPECT_FALSE(subtle::HeapState::IsMarking(*heap)); heap->StartIncrementalGarbageCollection(config); EXPECT_TRUE(subtle::HeapState::IsMarking(*heap)); heap->FinalizeIncrementalGarbageCollectionIfRunning(config); EXPECT_FALSE(subtle::HeapState::IsMarking(*heap)); heap->AsBase().sweeper().FinishIfRunning(); EXPECT_FALSE(subtle::HeapState::IsMarking(*heap)); } TEST_F(GCHeapTest, IsSweeping) { GCConfig config = GCConfig::PreciseIncrementalMarkingConcurrentSweepingConfig(); auto* heap = Heap::From(GetHeap()); EXPECT_FALSE(subtle::HeapState::IsSweeping(*heap)); heap->StartIncrementalGarbageCollection(config); EXPECT_FALSE(subtle::HeapState::IsSweeping(*heap)); heap->FinalizeIncrementalGarbageCollectionIfRunning(config); EXPECT_TRUE(subtle::HeapState::IsSweeping(*heap)); heap->AsBase().sweeper().FinishIfRunning(); EXPECT_FALSE(subtle::HeapState::IsSweeping(*heap)); } namespace { class GCedExpectSweepingOnOwningThread final : public GarbageCollected { public: explicit GCedExpectSweepingOnOwningThread(const HeapHandle& heap_handle) : heap_handle_(heap_handle) {} ~GCedExpectSweepingOnOwningThread() { EXPECT_TRUE(subtle::HeapState::IsSweepingOnOwningThread(heap_handle_)); } void Trace(Visitor*) const {} private: const HeapHandle& heap_handle_; }; } // namespace TEST_F(GCHeapTest, IsSweepingOnOwningThread) { GCConfig config = GCConfig::PreciseIncrementalMarkingConcurrentSweepingConfig(); auto* heap = Heap::From(GetHeap()); MakeGarbageCollected( heap->GetAllocationHandle(), *heap); EXPECT_FALSE(subtle::HeapState::IsSweepingOnOwningThread(*heap)); heap->StartIncrementalGarbageCollection(config); EXPECT_FALSE(subtle::HeapState::IsSweepingOnOwningThread(*heap)); heap->FinalizeIncrementalGarbageCollectionIfRunning(config); EXPECT_FALSE(subtle::HeapState::IsSweepingOnOwningThread(*heap)); heap->AsBase().sweeper().FinishIfRunning(); EXPECT_FALSE(subtle::HeapState::IsSweepingOnOwningThread(*heap)); } namespace { class ExpectAtomicPause final : public GarbageCollected { CPPGC_USING_PRE_FINALIZER(ExpectAtomicPause, PreFinalizer); public: explicit ExpectAtomicPause(HeapHandle& handle) : handle_(handle) {} ~ExpectAtomicPause() { EXPECT_TRUE(subtle::HeapState::IsInAtomicPause(handle_)); } void PreFinalizer() { EXPECT_TRUE(subtle::HeapState::IsInAtomicPause(handle_)); } void Trace(Visitor*) const {} private: HeapHandle& handle_; }; } // namespace TEST_F(GCHeapTest, IsInAtomicPause) { GCConfig config = GCConfig::PreciseIncrementalConfig(); auto* heap = Heap::From(GetHeap()); MakeGarbageCollected(heap->object_allocator(), *heap); EXPECT_FALSE(subtle::HeapState::IsInAtomicPause(*heap)); heap->StartIncrementalGarbageCollection(config); EXPECT_FALSE(subtle::HeapState::IsInAtomicPause(*heap)); heap->FinalizeIncrementalGarbageCollectionIfRunning(config); EXPECT_FALSE(subtle::HeapState::IsInAtomicPause(*heap)); heap->AsBase().sweeper().FinishIfRunning(); EXPECT_FALSE(subtle::HeapState::IsInAtomicPause(*heap)); } TEST_F(GCHeapTest, TerminateEmptyHeap) { Heap::From(GetHeap())->Terminate(); } TEST_F(GCHeapTest, TerminateClearsPersistent) { Persistent foo = MakeGarbageCollected(GetAllocationHandle()); EXPECT_TRUE(foo.Get()); Heap::From(GetHeap())->Terminate(); EXPECT_FALSE(foo.Get()); } TEST_F(GCHeapTest, TerminateInvokesDestructor) { Persistent foo = MakeGarbageCollected(GetAllocationHandle()); EXPECT_EQ(0u, Foo::destructor_callcount); Heap::From(GetHeap())->Terminate(); EXPECT_EQ(1u, Foo::destructor_callcount); } namespace { template