Upload Kmake

This commit is contained in:
Gorochu
2026-05-26 23:36:42 -07:00
parent ba051b2f74
commit 555ec72358
41615 changed files with 13344630 additions and 1 deletions

View File

@ -0,0 +1,224 @@
// Copyright 2022 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 <memory>
#include <vector>
#include "include/cppgc/internal/caged-heap-local-data.h"
#include "include/cppgc/internal/caged-heap.h"
#include "src/base/logging.h"
#include "src/heap/cppgc/heap-page.h"
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc::internal {
namespace {
class AgeTableTest : public testing::TestSupportingAllocationOnly {
public:
using Age = AgeTable::Age;
using AdjacentCardsPolicy = AgeTable::AdjacentCardsPolicy;
static constexpr auto kCardSizeInBytes = AgeTable::kCardSizeInBytes;
AgeTableTest() : age_table_(CagedHeapLocalData::Get().age_table) {
CagedHeap::CommitAgeTable(*(GetPlatform().GetPageAllocator()));
}
~AgeTableTest() override { age_table_.ResetForTesting(); }
NormalPage* AllocateNormalPage() {
RawHeap& heap = Heap::From(GetHeap())->raw_heap();
auto* space = static_cast<NormalPageSpace*>(
heap.Space(RawHeap::RegularSpaceType::kNormal1));
auto* page =
NormalPage::TryCreate(*Heap::From(GetHeap())->page_backend(), *space);
CHECK_NOT_NULL(page);
allocated_pages_.push_back({page, DestroyPage});
return page;
}
LargePage* AllocateLargePage() {
constexpr size_t kObjectSize = 2 * kLargeObjectSizeThreshold;
RawHeap& heap = Heap::From(GetHeap())->raw_heap();
auto* space = static_cast<LargePageSpace*>(
heap.Space(RawHeap::RegularSpaceType::kLarge));
auto* page = LargePage::TryCreate(*Heap::From(GetHeap())->page_backend(),
*space, kObjectSize);
CHECK_NOT_NULL(page);
allocated_pages_.push_back({page, DestroyPage});
return page;
}
void SetAgeForAddressRange(void* begin, void* end, Age age,
AdjacentCardsPolicy adjacent_cards_policy) {
age_table_.SetAgeForRange(CagedHeap::OffsetFromAddress(begin),
CagedHeap::OffsetFromAddress(end), age,
adjacent_cards_policy);
}
Age GetAge(void* ptr) const {
return age_table_.GetAge(CagedHeap::OffsetFromAddress(ptr));
}
void SetAge(void* ptr, Age age) {
age_table_.SetAge(CagedHeap::OffsetFromAddress(ptr), age);
}
void AssertAgeForAddressRange(void* begin, void* end, Age age) {
const uintptr_t offset_begin = CagedHeap::OffsetFromAddress(begin);
const uintptr_t offset_end = CagedHeap::OffsetFromAddress(end);
for (auto offset = RoundDown(offset_begin, kCardSizeInBytes);
offset < RoundUp(offset_end, kCardSizeInBytes);
offset += kCardSizeInBytes)
EXPECT_EQ(age, age_table_.GetAge(offset));
}
private:
static void DestroyPage(BasePage* page) { BasePage::Destroy(page); }
std::vector<std::unique_ptr<BasePage, void (*)(BasePage*)>> allocated_pages_;
AgeTable& age_table_;
};
} // namespace
TEST_F(AgeTableTest, SetAgeForNormalPage) {
auto* page = AllocateNormalPage();
// By default, everything is old.
AssertAgeForAddressRange(page->PayloadStart(), page->PayloadEnd(), Age::kOld);
// Set age for the entire page.
SetAgeForAddressRange(page->PayloadStart(), page->PayloadEnd(), Age::kYoung,
AdjacentCardsPolicy::kIgnore);
// Check that all cards have been set as young.
AssertAgeForAddressRange(page->PayloadStart(), page->PayloadEnd(),
Age::kYoung);
}
TEST_F(AgeTableTest, SetAgeForLargePage) {
auto* page = AllocateLargePage();
// By default, everything is old.
AssertAgeForAddressRange(page->PayloadStart(), page->PayloadEnd(), Age::kOld);
// Set age for the entire page.
SetAgeForAddressRange(page->PayloadStart(), page->PayloadEnd(), Age::kYoung,
AdjacentCardsPolicy::kIgnore);
// Check that all cards have been set as young.
AssertAgeForAddressRange(page->PayloadStart(), page->PayloadEnd(),
Age::kYoung);
}
TEST_F(AgeTableTest, SetAgeForSingleCardWithUnalignedAddresses) {
auto* page = AllocateNormalPage();
Address object_begin = reinterpret_cast<Address>(
RoundUp(reinterpret_cast<uintptr_t>(page->PayloadStart()),
kCardSizeInBytes) +
1);
Address object_end = object_begin + kCardSizeInBytes / 2;
EXPECT_EQ(Age::kOld, GetAge(object_begin));
// Try mark the card as young. This will mark the card as kMixed, since the
// card was previously marked as old.
SetAgeForAddressRange(object_begin, object_end, Age::kYoung,
AdjacentCardsPolicy::kConsider);
EXPECT_EQ(Age::kMixed, GetAge(object_begin));
SetAge(object_begin, Age::kOld);
// Try mark as old, but ignore ages of outer cards.
SetAgeForAddressRange(object_begin, object_end, Age::kYoung,
AdjacentCardsPolicy::kIgnore);
EXPECT_EQ(Age::kYoung, GetAge(object_begin));
}
TEST_F(AgeTableTest, SetAgeForSingleCardWithAlignedAddresses) {
auto* page = AllocateNormalPage();
Address object_begin = reinterpret_cast<Address>(RoundUp(
reinterpret_cast<uintptr_t>(page->PayloadStart()), kCardSizeInBytes));
Address object_end = object_begin + kCardSizeInBytes;
EXPECT_EQ(Age::kOld, GetAge(object_begin));
EXPECT_EQ(Age::kOld, GetAge(object_end));
// Try mark the card as young. This will mark the entire card as kYoung, since
// it's aligned.
SetAgeForAddressRange(object_begin, object_end, Age::kYoung,
AdjacentCardsPolicy::kConsider);
EXPECT_EQ(Age::kYoung, GetAge(object_begin));
// The end card should not be touched.
EXPECT_EQ(Age::kOld, GetAge(object_end));
}
TEST_F(AgeTableTest, SetAgeForSingleCardWithAlignedBeginButUnalignedEnd) {
auto* page = AllocateNormalPage();
Address object_begin = reinterpret_cast<Address>(RoundUp(
reinterpret_cast<uintptr_t>(page->PayloadStart()), kCardSizeInBytes));
Address object_end = object_begin + kCardSizeInBytes + 1;
EXPECT_EQ(Age::kOld, GetAge(object_begin));
EXPECT_EQ(Age::kOld, GetAge(object_end));
// Try mark the card as young. This will mark the entire card as kYoung, since
// it's aligned.
SetAgeForAddressRange(object_begin, object_end, Age::kYoung,
AdjacentCardsPolicy::kConsider);
EXPECT_EQ(Age::kYoung, GetAge(object_begin));
// The end card should be marked as mixed.
EXPECT_EQ(Age::kMixed, GetAge(object_end));
}
TEST_F(AgeTableTest, SetAgeForMultipleCardsWithUnalignedAddresses) {
static constexpr size_t kNumberOfCards = 4;
auto* page = AllocateNormalPage();
Address object_begin = reinterpret_cast<Address>(
RoundUp(reinterpret_cast<uintptr_t>(page->PayloadStart()),
kCardSizeInBytes) +
kCardSizeInBytes / 2);
Address object_end = object_begin + kNumberOfCards * kCardSizeInBytes;
AssertAgeForAddressRange(object_begin, object_end, Age::kOld);
// Try mark the cards as young. The inner 2 cards must be marked as young, the
// outer cards will be marked as mixed.
SetAgeForAddressRange(object_begin, object_end, Age::kYoung,
AdjacentCardsPolicy::kConsider);
EXPECT_EQ(Age::kMixed, GetAge(object_begin));
EXPECT_EQ(Age::kYoung, GetAge(object_begin + kCardSizeInBytes));
EXPECT_EQ(Age::kYoung, GetAge(object_begin + 2 * kCardSizeInBytes));
EXPECT_EQ(Age::kMixed, GetAge(object_end));
}
TEST_F(AgeTableTest, SetAgeForMultipleCardsConsiderAdjacentCards) {
static constexpr size_t kNumberOfCards = 4;
auto* page = AllocateNormalPage();
Address object_begin = reinterpret_cast<Address>(
RoundUp(reinterpret_cast<uintptr_t>(page->PayloadStart()),
kCardSizeInBytes) +
kCardSizeInBytes / 2);
Address object_end = object_begin + kNumberOfCards * kCardSizeInBytes;
// Mark the first and the last card as young.
SetAge(object_begin, Age::kYoung);
SetAge(object_end, Age::kYoung);
// Mark all the cards as young. The inner 2 cards must be marked as young, the
// outer cards will also be marked as young.
SetAgeForAddressRange(object_begin, object_end, Age::kYoung,
AdjacentCardsPolicy::kConsider);
EXPECT_EQ(Age::kYoung, GetAge(object_begin));
EXPECT_EQ(Age::kYoung, GetAge(object_begin + kCardSizeInBytes));
EXPECT_EQ(Age::kYoung, GetAge(object_begin + 2 * kCardSizeInBytes));
EXPECT_EQ(Age::kYoung, GetAge(object_end));
}
TEST_F(AgeTableTest, MarkAllCardsAsYoung) {
uint8_t* heap_start = reinterpret_cast<uint8_t*>(CagedHeapBase::GetBase());
void* heap_end =
heap_start + api_constants::kCagedHeapDefaultReservationSize - 1;
AssertAgeForAddressRange(heap_start, heap_end, Age::kOld);
SetAgeForAddressRange(heap_start, heap_end, Age::kYoung,
AdjacentCardsPolicy::kIgnore);
AssertAgeForAddressRange(heap_start, heap_end, Age::kYoung);
}
TEST_F(AgeTableTest, AgeTableSize) {
// The default cage size should yield a 1MB table.
EXPECT_EQ(1 * kMB, CagedHeapBase::GetAgeTableSize());
// Pretend there's a larger cage and verify that the age table reserves the
// correct amount of space for itself.
size_t age_table_size = AgeTable::CalculateAgeTableSizeForHeapSize(
api_constants::kCagedHeapDefaultReservationSize * 4);
EXPECT_EQ(4 * kMB, age_table_size);
}
} // namespace cppgc::internal

View File

@ -0,0 +1,249 @@
// Copyright 2021 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 "include/cppgc/visitor.h"
#include "src/heap/cppgc/globals.h"
#include "src/heap/cppgc/heap-object-header.h"
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace {
class CppgcAllocationTest : public testing::TestWithHeap {};
struct GCed final : GarbageCollected<GCed> {
void Trace(cppgc::Visitor*) const {}
};
class HeapAllocatedArray final : public GarbageCollected<HeapAllocatedArray> {
public:
HeapAllocatedArray() {
for (int i = 0; i < kArraySize; ++i) {
array_[i] = i % 128;
}
}
int8_t at(size_t i) { return array_[i]; }
void Trace(Visitor* visitor) const {}
private:
static const int kArraySize = 1000;
int8_t array_[kArraySize];
};
} // namespace
TEST_F(CppgcAllocationTest, MakeGarbageCollectedPreservesPayload) {
// Allocate an object in the heap.
HeapAllocatedArray* array =
MakeGarbageCollected<HeapAllocatedArray>(GetAllocationHandle());
// Sanity check of the contents in the heap.
EXPECT_EQ(0, array->at(0));
EXPECT_EQ(42, array->at(42));
EXPECT_EQ(0, array->at(128));
EXPECT_EQ(999 % 128, array->at(999));
}
TEST_F(CppgcAllocationTest, ReuseMemoryFromFreelist) {
// Allocate 3 objects so that the address we look for below is not at the
// start of the page.
MakeGarbageCollected<GCed>(GetAllocationHandle());
MakeGarbageCollected<GCed>(GetAllocationHandle());
GCed* p1 = MakeGarbageCollected<GCed>(GetAllocationHandle());
// GC reclaims all objects. LABs are reset during the GC.
PreciseGC();
// Now the freed memory in the first GC should be reused. Allocating 3
// objects again should suffice but allocating 5 to give the test some slack.
bool reused_memory_found = false;
for (int i = 0; i < 5; i++) {
GCed* p2 = MakeGarbageCollected<GCed>(GetAllocationHandle());
if (p1 == p2) {
reused_memory_found = true;
break;
}
}
EXPECT_TRUE(reused_memory_found);
}
namespace {
class CallbackInCtor final : public GarbageCollected<CallbackInCtor> {
public:
template <typename Callback>
explicit CallbackInCtor(Callback callback) {
callback();
}
void Trace(Visitor*) const {}
};
} // namespace
TEST_F(CppgcAllocationTest,
ConservativeGCDuringAllocationDoesNotReclaimObject) {
CallbackInCtor* obj = MakeGarbageCollected<CallbackInCtor>(
GetAllocationHandle(), [this]() { ConservativeGC(); });
EXPECT_FALSE(HeapObjectHeader::FromObject(obj).IsFree());
}
// The test below requires that a large object is reused in the GC. This only
// reliably works on 64-bit builds using caged heap. On 32-bit builds large
// objects are mapped in individually and returned to the OS as a whole on
// reclamation.
#if defined(CPPGC_CAGED_HEAP)
namespace {
class LargeObjectCheckingPayloadForZeroMemory final
: public GarbageCollected<LargeObjectCheckingPayloadForZeroMemory> {
public:
static constexpr size_t kDataSize = kLargeObjectSizeThreshold + 1;
static size_t destructor_calls;
LargeObjectCheckingPayloadForZeroMemory() {
for (size_t i = 0; i < kDataSize; ++i) {
EXPECT_EQ(0, data[i]);
}
}
~LargeObjectCheckingPayloadForZeroMemory() { ++destructor_calls; }
void Trace(Visitor*) const {}
char data[kDataSize];
};
size_t LargeObjectCheckingPayloadForZeroMemory::destructor_calls = 0u;
} // namespace
TEST_F(CppgcAllocationTest, LargePagesAreZeroedOut) {
LargeObjectCheckingPayloadForZeroMemory::destructor_calls = 0u;
auto* initial_object =
MakeGarbageCollected<LargeObjectCheckingPayloadForZeroMemory>(
GetAllocationHandle());
memset(initial_object->data, 0xff,
LargeObjectCheckingPayloadForZeroMemory::kDataSize);
// GC ignores stack and thus frees the object.
PreciseGC();
EXPECT_EQ(1u, LargeObjectCheckingPayloadForZeroMemory::destructor_calls);
auto* new_object =
MakeGarbageCollected<LargeObjectCheckingPayloadForZeroMemory>(
GetAllocationHandle());
// If the following check fails, then the GC didn't reuse the underlying page
// and the test doesn't check anything.
EXPECT_EQ(initial_object, new_object);
}
#endif // defined(CPPGC_CAGED_HEAP)
namespace {
constexpr size_t kDoubleWord = 2 * sizeof(void*);
constexpr size_t kWord = sizeof(void*);
class alignas(kDoubleWord) DoubleWordAligned final
: public GarbageCollected<DoubleWordAligned> {
public:
void Trace(Visitor*) const {}
};
class alignas(kDoubleWord) LargeDoubleWordAligned
: public GarbageCollected<LargeDoubleWordAligned> {
public:
virtual void Trace(cppgc::Visitor*) const {}
char array[kLargeObjectSizeThreshold];
};
template <size_t Size>
class CustomPadding final : public GarbageCollected<CustomPadding<Size>> {
public:
void Trace(cppgc::Visitor* visitor) const {}
char base_size[128]; // Gets allocated in using RegularSpaceType::kNormal4.
char padding[Size];
};
template <size_t Size>
class alignas(kDoubleWord) AlignedCustomPadding final
: public GarbageCollected<AlignedCustomPadding<Size>> {
public:
void Trace(cppgc::Visitor* visitor) const {}
char base_size[128]; // Gets allocated in using RegularSpaceType::kNormal4.
char padding[Size];
};
} // namespace
TEST_F(CppgcAllocationTest, DoubleWordAlignedAllocation) {
static constexpr size_t kAlignmentMask = kDoubleWord - 1;
auto* gced = MakeGarbageCollected<DoubleWordAligned>(GetAllocationHandle());
EXPECT_EQ(0u, reinterpret_cast<uintptr_t>(gced) & kAlignmentMask);
}
TEST_F(CppgcAllocationTest, LargeDoubleWordAlignedAllocation) {
static constexpr size_t kAlignmentMask = kDoubleWord - 1;
auto* gced =
MakeGarbageCollected<LargeDoubleWordAligned>(GetAllocationHandle());
EXPECT_EQ(0u, reinterpret_cast<uintptr_t>(gced) & kAlignmentMask);
}
TEST_F(CppgcAllocationTest, AlignToDoubleWordFromUnaligned) {
static constexpr size_t kAlignmentMask = kDoubleWord - 1;
// The address from which the next object can be allocated, i.e. the end of
// |padding_object|, should not be double-word aligned. Allocate extra objects
// to ensure padding in case payload start is 16-byte aligned.
using PaddingObject = CustomPadding<kDoubleWord>;
static_assert(((sizeof(HeapObjectHeader) + sizeof(PaddingObject)) %
kDoubleWord) == kWord);
void* padding_object = nullptr;
if (NormalPage::PayloadSize() % kDoubleWord == 0) {
padding_object = MakeGarbageCollected<PaddingObject>(GetAllocationHandle());
ASSERT_EQ(kWord, (reinterpret_cast<uintptr_t>(padding_object) +
sizeof(PaddingObject)) &
kAlignmentMask);
}
auto* aligned_object =
MakeGarbageCollected<AlignedCustomPadding<16>>(GetAllocationHandle());
EXPECT_EQ(0u, reinterpret_cast<uintptr_t>(aligned_object) & kAlignmentMask);
if (padding_object) {
// Test only yielded a reliable result if objects are adjacent to each
// other.
ASSERT_EQ(reinterpret_cast<uintptr_t>(padding_object) +
sizeof(PaddingObject) + sizeof(HeapObjectHeader),
reinterpret_cast<uintptr_t>(aligned_object));
}
}
TEST_F(CppgcAllocationTest, AlignToDoubleWordFromAligned) {
static constexpr size_t kAlignmentMask = kDoubleWord - 1;
// The address from which the next object can be allocated, i.e. the end of
// |padding_object|, should be double-word aligned. Allocate extra objects to
// ensure padding in case payload start is 8-byte aligned.
using PaddingObject = CustomPadding<kDoubleWord>;
static_assert(((sizeof(HeapObjectHeader) + sizeof(PaddingObject)) %
kDoubleWord) == kWord);
void* padding_object = nullptr;
if (NormalPage::PayloadSize() % kDoubleWord == kWord) {
padding_object = MakeGarbageCollected<PaddingObject>(GetAllocationHandle());
ASSERT_EQ(0u, (reinterpret_cast<uintptr_t>(padding_object) +
sizeof(PaddingObject)) &
kAlignmentMask);
}
auto* aligned_object =
MakeGarbageCollected<AlignedCustomPadding<16>>(GetAllocationHandle());
EXPECT_EQ(0u, reinterpret_cast<uintptr_t>(aligned_object) & kAlignmentMask);
if (padding_object) {
// Test only yielded a reliable result if objects are adjacent to each
// other.
ASSERT_EQ(reinterpret_cast<uintptr_t>(padding_object) +
sizeof(PaddingObject) + 2 * sizeof(HeapObjectHeader),
reinterpret_cast<uintptr_t>(aligned_object));
}
}
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,38 @@
// Copyright 2024 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.
#if defined(CPPGC_CAGED_HEAP)
#include "src/heap/cppgc/caged-heap.h"
#include "include/cppgc/internal/caged-heap-local-data.h"
#include "src/base/page-allocator.h"
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc::internal {
class CagedHeapDeathTest : public testing::TestWithHeap {};
TEST_F(CagedHeapDeathTest, AgeTableUncommittedBeforeGenerationalGCEnabled) {
// Test cannot run if Generational GC was already enabled.
ASSERT_FALSE(Heap::From(GetHeap())->generational_gc_supported());
EXPECT_DEATH_IF_SUPPORTED(
CagedHeapLocalData::Get().age_table.SetAge(0, AgeTable::Age::kOld), "");
}
class CagedHeapTest : public testing::TestWithHeap {};
TEST_F(CagedHeapTest, AgeTableCommittedAfterGenerationalGCEnabled) {
// Test cannot run if Generational GC was already enabled.
ASSERT_FALSE(Heap::From(GetHeap())->generational_gc_supported());
CagedHeap::CommitAgeTable(*(GetPlatform().GetPageAllocator()));
EXPECT_EQ(CagedHeapLocalData::Get().age_table.GetAge(0), AgeTable::Age::kOld);
}
} // namespace cppgc::internal
#endif // defined(CPPGC_CAGED_HEAP)

View File

@ -0,0 +1,254 @@
// 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/member.h"
#include "include/cppgc/persistent.h"
#include "src/heap/cppgc/garbage-collector.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;
subtle::UncompressedMember<CompactableGCed> other = nullptr;
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());
}
}
subtle::UncompressedMember<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(GCConfig::MarkingType::kIncremental,
StackState::kNoHeapPointers);
EXPECT_TRUE(compactor().IsEnabledForTesting());
}
void FinishCompaction() { compactor().CompactSpacesIfEnabled(); }
void StartGC() {
CompactableGCed::g_destructor_callcount = 0u;
StartCompaction();
heap()->StartIncrementalGarbageCollection(
GCConfig::PreciseIncrementalConfig());
}
void EndGC() {
heap()->marker()->FinishMarking(StackState::kNoHeapPointers);
heap()->GetMarkerRefForTesting().reset();
FinishCompaction();
// Sweeping also verifies the object start bitmap.
const SweepingConfig sweeping_config{
SweepingConfig::SweepingType::kAtomic,
SweepingConfig::CompactableSpaceHandling::kIgnore};
heap()->sweeper().Start(sweeping_config);
heap()->sweeper().FinishIfRunning();
}
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();
heap()->stats_collector()->NotifyMarkingStarted(
CollectionType::kMajor, GCConfig::MarkingType::kAtomic,
GCConfig::IsForcedGC::kNotForced);
heap()->stats_collector()->NotifyMarkingCompleted(0);
FinishCompaction();
heap()->stats_collector()->NotifySweepingCompleted(
GCConfig::SweepingType::kAtomic);
}
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]));
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);
}
TEST_F(CompactorTest, OnStackSlotShouldBeFiltered) {
StartGC();
const CompactableGCed* compactable_object =
MakeGarbageCollected<CompactableGCed>(GetAllocationHandle());
heap()->marker()->Visitor().RegisterMovableReference(&compactable_object);
EndGC();
}
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,198 @@
// 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 "include/cppgc/default-platform.h"
#include "include/cppgc/member.h"
#include "include/cppgc/persistent.h"
#include "src/heap/cppgc/globals.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 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)
void StartConcurrentGC() {
Heap* heap = Heap::From(GetHeap());
heap->DisableHeapGrowingForTesting();
heap->StartIncrementalGarbageCollection(
GCConfig::PreciseConcurrentConfig());
heap->marker()->SetMainThreadMarkingDisabledForTesting(true);
}
bool SingleStep(StackState stack_state) {
MarkerBase* marker = Heap::From(GetHeap())->marker();
DCHECK(marker);
return marker->IncrementalMarkingStepForTesting(stack_state);
}
void FinishGC() {
Heap* heap = Heap::From(GetHeap());
heap->marker()->SetMainThreadMarkingDisabledForTesting(false);
heap->FinalizeIncrementalGarbageCollectionIfRunning(
GCConfig::PreciseConcurrentConfig());
}
};
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) {
StartConcurrentGC();
Persistent<GCedHolder<GCed>> root =
MakeGarbageCollected<GCedHolder<GCed>>(GetAllocationHandle());
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());
last_object = &(*last_object)->child_;
}
// Use SingleStep to re-post concurrent jobs.
SingleStep(StackState::kNoHeapPointers);
}
FinishGC();
}
TEST_F(ConcurrentMarkingTest, MarkingInConstructionObjects) {
StartConcurrentGC();
Persistent<GCedHolder<GCedWithCallback>> root =
MakeGarbageCollected<GCedHolder<GCedWithCallback>>(GetAllocationHandle());
Member<GCedWithCallback>* last_object = &root->object;
for (int i = 0; i < kNumStep; ++i) {
for (int j = 0; j < kNumStep; ++j) {
MakeGarbageCollected<GCedWithCallback>(
GetAllocationHandle(), [&last_object](GCedWithCallback* obj) {
*last_object = obj;
last_object = &(*last_object)->child_;
});
}
// Use SingleStep to re-post concurrent jobs.
SingleStep(StackState::kNoHeapPointers);
}
FinishGC();
}
TEST_F(ConcurrentMarkingTest, MarkingMixinObjects) {
StartConcurrentGC();
Persistent<GCedHolder<Mixin>> root =
MakeGarbageCollected<GCedHolder<Mixin>>(GetAllocationHandle());
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());
last_object = &(*last_object)->child_;
}
// Use SingleStep to re-post concurrent jobs.
SingleStep(StackState::kNoHeapPointers);
}
FinishGC();
}
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

View File

@ -0,0 +1,416 @@
// 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 <algorithm>
#include <set>
#include <vector>
#include "include/cppgc/allocation.h"
#include "include/cppgc/platform.h"
#include "include/v8-platform.h"
#include "src/heap/cppgc/globals.h"
#include "src/heap/cppgc/heap-object-header.h"
#include "src/heap/cppgc/heap-page.h"
#include "src/heap/cppgc/heap-space.h"
#include "src/heap/cppgc/heap-visitor.h"
#include "src/heap/cppgc/page-memory.h"
#include "src/heap/cppgc/raw-heap.h"
#include "src/heap/cppgc/stats-collector.h"
#include "src/heap/cppgc/sweeper.h"
#include "test/unittests/heap/cppgc/test-platform.h"
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace {
size_t g_destructor_callcount;
template <size_t Size>
class Finalizable : public GarbageCollected<Finalizable<Size>> {
public:
Finalizable() : creation_thread_{v8::base::OS::GetCurrentThreadId()} {}
virtual ~Finalizable() {
++g_destructor_callcount;
EXPECT_EQ(creation_thread_, v8::base::OS::GetCurrentThreadId());
}
virtual void Trace(cppgc::Visitor*) const {}
private:
char array_[Size];
int creation_thread_;
};
using NormalFinalizable = Finalizable<32>;
using LargeFinalizable = Finalizable<kLargeObjectSizeThreshold * 2>;
template <size_t Size>
class NonFinalizable : public GarbageCollected<NonFinalizable<Size>> {
public:
virtual void Trace(cppgc::Visitor*) const {}
private:
char array_[Size];
int padding_to_make_size_the_same_as_finalizible_;
};
using NormalNonFinalizable = NonFinalizable<32>;
using LargeNonFinalizable = NonFinalizable<kLargeObjectSizeThreshold * 2>;
} // namespace
class ConcurrentSweeperTest : public testing::TestWithHeap {
public:
ConcurrentSweeperTest() { g_destructor_callcount = 0; }
void StartSweeping() {
Heap* heap = Heap::From(GetHeap());
ResetLinearAllocationBuffers();
// Pretend do finish marking as StatsCollector verifies that Notify*
// methods are called in the right order.
heap->stats_collector()->NotifyMarkingStarted(
CollectionType::kMajor, GCConfig::MarkingType::kAtomic,
GCConfig::IsForcedGC::kNotForced);
heap->stats_collector()->NotifyMarkingCompleted(0);
Sweeper& sweeper = heap->sweeper();
const SweepingConfig sweeping_config{
SweepingConfig::SweepingType::kIncrementalAndConcurrent,
SweepingConfig::CompactableSpaceHandling::kSweep};
sweeper.Start(sweeping_config);
}
void WaitForConcurrentSweeping() {
Heap* heap = Heap::From(GetHeap());
Sweeper& sweeper = heap->sweeper();
sweeper.WaitForConcurrentSweepingForTesting();
}
void FinishSweeping() {
Heap* heap = Heap::From(GetHeap());
Sweeper& sweeper = heap->sweeper();
sweeper.FinishIfRunning();
}
const RawHeap& GetRawHeap() const {
const Heap* heap = Heap::From(GetHeap());
return heap->raw_heap();
}
void CheckFreeListEntries(const std::vector<void*>& objects) {
const Heap* heap = Heap::From(GetHeap());
const PageBackend* backend = heap->page_backend();
for (auto* object : objects) {
// The corresponding page could be removed.
if (!backend->Lookup(static_cast<ConstAddress>(object))) continue;
const auto* header =
BasePage::FromPayload(object)->TryObjectHeaderFromInnerAddress(
object);
// TryObjectHeaderFromInnerAddress returns nullptr for freelist entries.
EXPECT_EQ(nullptr, header);
}
}
bool PageInBackend(const BasePage* page) {
const Heap* heap = Heap::From(GetHeap());
const PageBackend* backend = heap->page_backend();
return backend->Lookup(reinterpret_cast<ConstAddress>(page));
}
bool FreeListContains(const BaseSpace& space,
const std::vector<void*>& objects) {
const Heap* heap = Heap::From(GetHeap());
const PageBackend* backend = heap->page_backend();
const auto& freelist = NormalPageSpace::From(space).free_list();
for (void* object : objects) {
// The corresponding page could be removed.
if (!backend->Lookup(static_cast<ConstAddress>(object))) continue;
if (!freelist.ContainsForTesting({object, 0})) return false;
}
return true;
}
void MarkObject(void* payload) {
HeapObjectHeader& header = HeapObjectHeader::FromObject(payload);
header.TryMarkAtomic();
BasePage* page = BasePage::FromPayload(&header);
page->IncrementMarkedBytes(page->is_large()
? LargePage::From(page)->PayloadSize()
: header.AllocatedSize());
}
};
TEST_F(ConcurrentSweeperTest, BackgroundSweepOfNormalPage) {
// Non finalizable objects are swept right away.
using GCedType = NormalNonFinalizable;
auto* unmarked_object = MakeGarbageCollected<GCedType>(GetAllocationHandle());
auto* marked_object = MakeGarbageCollected<GCedType>(GetAllocationHandle());
MarkObject(marked_object);
auto* page = BasePage::FromPayload(unmarked_object);
auto& space = page->space();
// The test requires objects to be allocated on the same page;
ASSERT_EQ(page, BasePage::FromPayload(marked_object));
StartSweeping();
// Wait for concurrent sweeping to finish.
WaitForConcurrentSweeping();
const auto& hoh = HeapObjectHeader::FromObject(marked_object);
if (Heap::From(GetHeap())->generational_gc_supported()) {
// Check that the marked object is still marked.
EXPECT_TRUE(hoh.IsMarked());
} else {
// Check that the marked object was unmarked.
EXPECT_FALSE(hoh.IsMarked());
}
// Check that free list entries are created right away for non-finalizable
// objects, but not immediately returned to the space's freelist.
CheckFreeListEntries({unmarked_object});
EXPECT_FALSE(FreeListContains(space, {unmarked_object}));
FinishSweeping();
// Check that finalizable objects are swept and put into the freelist of the
// corresponding space.
EXPECT_TRUE(FreeListContains(space, {unmarked_object}));
}
TEST_F(ConcurrentSweeperTest, BackgroundSweepOfLargePage) {
// Non finalizable objects are swept right away but the page is only returned
// from the main thread.
using GCedType = LargeNonFinalizable;
auto* unmarked_object = MakeGarbageCollected<GCedType>(GetAllocationHandle());
auto* marked_object = MakeGarbageCollected<GCedType>(GetAllocationHandle());
MarkObject(marked_object);
auto* unmarked_page = BasePage::FromPayload(unmarked_object);
auto* marked_page = BasePage::FromPayload(marked_object);
auto& space = unmarked_page->space();
ASSERT_EQ(&space, &marked_page->space());
StartSweeping();
// Wait for concurrent sweeping to finish.
WaitForConcurrentSweeping();
const auto& hoh = HeapObjectHeader::FromObject(marked_object);
if (Heap::From(GetHeap())->generational_gc_supported()) {
// Check that the marked object is still marked.
EXPECT_TRUE(hoh.IsMarked());
} else {
// Check that the marked object was unmarked.
EXPECT_FALSE(hoh.IsMarked());
}
// The page should not have been removed on the background threads.
EXPECT_TRUE(PageInBackend(unmarked_page));
FinishSweeping();
// Check that free list entries are created right away for non-finalizable
// objects, but not immediately returned to the space's freelist.
EXPECT_FALSE(PageInBackend(unmarked_page));
// Check that marked pages are returned to space right away.
EXPECT_NE(space.end(), std::find(space.begin(), space.end(), marked_page));
}
TEST_F(ConcurrentSweeperTest, DeferredFinalizationOfNormalPage) {
static constexpr size_t kNumberOfObjects = 10;
// Finalizable types are left intact by concurrent sweeper.
using GCedType = NormalFinalizable;
std::set<BasePage*> pages;
std::vector<void*> objects;
BaseSpace* space = nullptr;
for (size_t i = 0; i < kNumberOfObjects; ++i) {
auto* object = MakeGarbageCollected<GCedType>(GetAllocationHandle());
objects.push_back(object);
auto* page = BasePage::FromPayload(object);
pages.insert(page);
if (!space) space = &page->space();
}
StartSweeping();
// Wait for concurrent sweeping to finish.
WaitForConcurrentSweeping();
// Check that pages are not returned right away.
for (auto* page : pages) {
EXPECT_EQ(space->end(), std::find(space->begin(), space->end(), page));
}
// Check that finalizable objects are left intact in pages.
EXPECT_FALSE(FreeListContains(*space, objects));
// No finalizers have been executed.
EXPECT_EQ(0u, g_destructor_callcount);
FinishSweeping();
// Check that finalizable objects are swept and turned into freelist entries.
CheckFreeListEntries(objects);
// Check that space's freelist contains these entries.
EXPECT_TRUE(FreeListContains(*space, objects));
// Check that finalizers have been executed.
EXPECT_EQ(kNumberOfObjects, g_destructor_callcount);
}
TEST_F(ConcurrentSweeperTest, DeferredFinalizationOfLargePage) {
using GCedType = LargeFinalizable;
auto* object = MakeGarbageCollected<GCedType>(GetAllocationHandle());
auto* page = BasePage::FromPayload(object);
auto& space = page->space();
StartSweeping();
// Wait for concurrent sweeping to finish.
WaitForConcurrentSweeping();
// Check that the page is not returned to the space.
EXPECT_EQ(space.end(), std::find(space.begin(), space.end(), page));
// Check that no destructors have been executed yet.
EXPECT_EQ(0u, g_destructor_callcount);
FinishSweeping();
// Check that the destructor was executed.
EXPECT_EQ(1u, g_destructor_callcount);
// Check that page was unmapped.
EXPECT_FALSE(PageInBackend(page));
}
TEST_F(ConcurrentSweeperTest, DestroyLargePageOnMainThread) {
// This test fails with TSAN when large pages are destroyed concurrently
// without proper support by the backend.
using GCedType = LargeNonFinalizable;
auto* object = MakeGarbageCollected<GCedType>(GetAllocationHandle());
auto* page = BasePage::FromPayload(object);
StartSweeping();
// Allocating another large object should not race here.
MakeGarbageCollected<GCedType>(GetAllocationHandle());
// Wait for concurrent sweeping to finish.
WaitForConcurrentSweeping();
FinishSweeping();
// Check that page was unmapped.
EXPECT_FALSE(PageInBackend(page));
}
TEST_F(ConcurrentSweeperTest, IncrementalSweeping) {
testing::TestPlatform::DisableBackgroundTasksScope disable_concurrent_sweeper(
&GetPlatform());
auto task_runner =
GetPlatform().GetForegroundTaskRunner(TaskPriority::kUserBlocking);
// Create two unmarked objects.
MakeGarbageCollected<NormalFinalizable>(GetAllocationHandle());
MakeGarbageCollected<LargeFinalizable>(GetAllocationHandle());
// Create two marked objects.
auto* marked_normal_object =
MakeGarbageCollected<NormalFinalizable>(GetAllocationHandle());
auto* marked_large_object =
MakeGarbageCollected<LargeFinalizable>(GetAllocationHandle());
auto& marked_normal_header =
HeapObjectHeader::FromObject(marked_normal_object);
auto& marked_large_header = HeapObjectHeader::FromObject(marked_large_object);
MarkObject(marked_normal_object);
MarkObject(marked_large_object);
StartSweeping();
EXPECT_EQ(0u, g_destructor_callcount);
EXPECT_TRUE(marked_normal_header.IsMarked());
// Live large objects are eagerly swept.
if (Heap::From(GetHeap())->generational_gc_supported()) {
EXPECT_TRUE(marked_large_header.IsMarked());
} else {
EXPECT_FALSE(marked_large_header.IsMarked());
}
// Wait for incremental sweeper to finish.
GetPlatform().RunAllForegroundTasks();
EXPECT_EQ(2u, g_destructor_callcount);
if (Heap::From(GetHeap())->generational_gc_supported()) {
EXPECT_TRUE(marked_normal_header.IsMarked());
EXPECT_TRUE(marked_large_header.IsMarked());
} else {
EXPECT_FALSE(marked_normal_header.IsMarked());
EXPECT_FALSE(marked_large_header.IsMarked());
}
FinishSweeping();
}
TEST_F(ConcurrentSweeperTest, SweepOnAllocationReturnEmptyPage) {
PreciseGC();
// First, allocate the full page of finalizable objects.
const size_t objects_to_allocated =
NormalPage::PayloadSize() /
(sizeof(HeapObjectHeader) + sizeof(NormalFinalizable));
auto* first_obj =
MakeGarbageCollected<NormalFinalizable>(GetAllocationHandle());
auto* finalizable_page =
NormalPage::FromInnerAddress(&HeapBase::From(GetHeapHandle()), first_obj);
for (size_t i = 1; i < objects_to_allocated; ++i) {
MakeGarbageCollected<NormalFinalizable>(GetAllocationHandle());
}
// Then, allocate a new unfinalizable object on a fresh page. We do that so
// that the sweeper on allocation doesn't allocate a new page.
auto* non_finalizable =
MakeGarbageCollected<NormalNonFinalizable>(GetAllocationHandle());
auto* non_finalizable_page = NormalPage::FromInnerAddress(
&HeapBase::From(GetHeapHandle()), non_finalizable);
ASSERT_NE(finalizable_page, non_finalizable_page);
// Start the GC without sweeping.
static constexpr GCConfig config = {
CollectionType::kMajor, StackState::kNoHeapPointers,
GCConfig::MarkingType::kAtomic,
GCConfig::SweepingType::kIncrementalAndConcurrent};
Heap::From(GetHeap())->CollectGarbage(config);
WaitForConcurrentSweeping();
// Allocate and sweep.
auto* allocated_after_sweeping =
MakeGarbageCollected<NormalFinalizable>(GetAllocationHandle());
// Check that the empty page of finalizable objects was returned.
EXPECT_EQ(finalizable_page,
NormalPage::FromInnerAddress(&HeapBase::From(GetHeapHandle()),
allocated_after_sweeping));
}
} // namespace internal
} // namespace cppgc

View File

@ -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++; }
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

View File

@ -0,0 +1,279 @@
// 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 "include/cppgc/custom-space.h"
#include "src/heap/cppgc/heap-page.h"
#include "src/heap/cppgc/raw-heap.h"
#include "test/unittests/heap/cppgc/tests.h"
namespace cppgc {
class CustomSpace1 : public CustomSpace<CustomSpace1> {
public:
static constexpr size_t kSpaceIndex = 0;
};
class CustomSpace2 : public CustomSpace<CustomSpace2> {
public:
static constexpr size_t kSpaceIndex = 1;
};
namespace internal {
namespace {
size_t g_destructor_callcount;
class TestWithHeapWithCustomSpaces : public testing::TestWithPlatform {
protected:
TestWithHeapWithCustomSpaces() {
Heap::HeapOptions options;
options.custom_spaces.emplace_back(std::make_unique<CustomSpace1>());
options.custom_spaces.emplace_back(std::make_unique<CustomSpace2>());
heap_ = Heap::Create(platform_, std::move(options));
g_destructor_callcount = 0;
}
void PreciseGC() {
heap_->ForceGarbageCollectionSlow(
::testing::UnitTest::GetInstance()->current_test_info()->name(),
"Testing", cppgc::Heap::StackState::kNoHeapPointers);
}
cppgc::Heap* GetHeap() const { return heap_.get(); }
private:
std::unique_ptr<cppgc::Heap> heap_;
};
class RegularGCed final : public GarbageCollected<RegularGCed> {
public:
void Trace(Visitor*) const {}
};
class CustomGCed1 final : public GarbageCollected<CustomGCed1> {
public:
~CustomGCed1() { g_destructor_callcount++; }
void Trace(Visitor*) const {}
};
class CustomGCed2 final : public GarbageCollected<CustomGCed2> {
public:
~CustomGCed2() { g_destructor_callcount++; }
void Trace(Visitor*) const {}
};
class CustomGCedBase : public GarbageCollected<CustomGCedBase> {
public:
void Trace(Visitor*) const {}
};
class CustomGCedFinal1 final : public CustomGCedBase {
public:
~CustomGCedFinal1() { g_destructor_callcount++; }
};
class CustomGCedFinal2 final : public CustomGCedBase {
public:
~CustomGCedFinal2() { g_destructor_callcount++; }
};
constexpr size_t kDoubleWord = 2 * sizeof(void*);
class alignas(kDoubleWord) CustomGCedWithDoubleWordAlignment final
: public GarbageCollected<CustomGCedWithDoubleWordAlignment> {
public:
void Trace(Visitor*) const {}
};
} // namespace
} // namespace internal
template <>
struct SpaceTrait<internal::CustomGCed1> {
using Space = CustomSpace1;
};
template <>
struct SpaceTrait<internal::CustomGCed2> {
using Space = CustomSpace2;
};
template <typename T>
struct SpaceTrait<
T, std::enable_if_t<std::is_base_of<internal::CustomGCedBase, T>::value>> {
using Space = CustomSpace1;
};
template <>
struct SpaceTrait<internal::CustomGCedWithDoubleWordAlignment> {
using Space = CustomSpace1;
};
namespace internal {
TEST_F(TestWithHeapWithCustomSpaces, AllocateOnCustomSpaces) {
auto* regular =
MakeGarbageCollected<RegularGCed>(GetHeap()->GetAllocationHandle());
auto* custom1 =
MakeGarbageCollected<CustomGCed1>(GetHeap()->GetAllocationHandle());
auto* custom2 =
MakeGarbageCollected<CustomGCed2>(GetHeap()->GetAllocationHandle());
EXPECT_EQ(RawHeap::kNumberOfRegularSpaces,
NormalPage::FromPayload(custom1)->space().index());
EXPECT_EQ(RawHeap::kNumberOfRegularSpaces + 1,
NormalPage::FromPayload(custom2)->space().index());
EXPECT_EQ(static_cast<size_t>(RawHeap::RegularSpaceType::kNormal1),
NormalPage::FromPayload(regular)->space().index());
}
TEST_F(TestWithHeapWithCustomSpaces, AllocateDoubleWordAlignedOnCustomSpace) {
static constexpr size_t kAlignmentMask = kDoubleWord - 1;
auto* custom_aligned =
MakeGarbageCollected<CustomGCedWithDoubleWordAlignment>(
GetHeap()->GetAllocationHandle());
EXPECT_EQ(0u, reinterpret_cast<uintptr_t>(custom_aligned) & kAlignmentMask);
}
TEST_F(TestWithHeapWithCustomSpaces, DifferentSpacesUsesDifferentPages) {
auto* regular =
MakeGarbageCollected<RegularGCed>(GetHeap()->GetAllocationHandle());
auto* custom1 =
MakeGarbageCollected<CustomGCed1>(GetHeap()->GetAllocationHandle());
auto* custom2 =
MakeGarbageCollected<CustomGCed2>(GetHeap()->GetAllocationHandle());
EXPECT_NE(NormalPage::FromPayload(regular), NormalPage::FromPayload(custom1));
EXPECT_NE(NormalPage::FromPayload(regular), NormalPage::FromPayload(custom2));
EXPECT_NE(NormalPage::FromPayload(custom1), NormalPage::FromPayload(custom2));
}
TEST_F(TestWithHeapWithCustomSpaces,
AllocateOnCustomSpacesSpecifiedThroughBase) {
auto* regular =
MakeGarbageCollected<RegularGCed>(GetHeap()->GetAllocationHandle());
auto* custom1 =
MakeGarbageCollected<CustomGCedFinal1>(GetHeap()->GetAllocationHandle());
auto* custom2 =
MakeGarbageCollected<CustomGCedFinal2>(GetHeap()->GetAllocationHandle());
EXPECT_EQ(RawHeap::kNumberOfRegularSpaces,
NormalPage::FromPayload(custom1)->space().index());
EXPECT_EQ(RawHeap::kNumberOfRegularSpaces,
NormalPage::FromPayload(custom2)->space().index());
EXPECT_EQ(static_cast<size_t>(RawHeap::RegularSpaceType::kNormal1),
NormalPage::FromPayload(regular)->space().index());
}
TEST_F(TestWithHeapWithCustomSpaces, SweepCustomSpace) {
MakeGarbageCollected<CustomGCedFinal1>(GetHeap()->GetAllocationHandle());
MakeGarbageCollected<CustomGCedFinal2>(GetHeap()->GetAllocationHandle());
MakeGarbageCollected<CustomGCed1>(GetHeap()->GetAllocationHandle());
MakeGarbageCollected<CustomGCed2>(GetHeap()->GetAllocationHandle());
EXPECT_EQ(0u, g_destructor_callcount);
PreciseGC();
EXPECT_EQ(4u, g_destructor_callcount);
}
} // 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

View File

@ -0,0 +1,272 @@
// 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 "include/cppgc/garbage-collected.h"
#include "include/cppgc/persistent.h"
#include "include/cppgc/visitor.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<EphemeronHolder> {
public:
EphemeronHolder(GCed* key, GCed* value) : ephemeron_pair_(key, value) {}
void Trace(cppgc::Visitor* visitor) const { visitor->Trace(ephemeron_pair_); }
const EphemeronPair<GCed, GCed>& ephemeron_pair() const {
return ephemeron_pair_;
}
private:
EphemeronPair<GCed, GCed> ephemeron_pair_;
};
class EphemeronPairTest : public testing::TestWithHeap {
static constexpr MarkingConfig IncrementalPreciseMarkingConfig = {
CollectionType::kMajor, StackState::kNoHeapPointers,
MarkingConfig::MarkingType::kIncremental};
public:
void FinishSteps() {
while (!SingleStep()) {
}
}
void FinishMarking() {
marker_->FinishMarking(StackState::kNoHeapPointers);
// Pretend do finish sweeping as StatsCollector verifies that Notify*
// methods are called in the right order.
Heap::From(GetHeap())->stats_collector()->NotifySweepingCompleted(
GCConfig::SweepingType::kIncremental);
}
void InitializeMarker(HeapBase& heap, cppgc::Platform* platform) {
marker_ = std::make_unique<Marker>(heap, platform,
IncrementalPreciseMarkingConfig);
marker_->StartMarking();
}
Marker* marker() const { return marker_.get(); }
private:
bool SingleStep() {
return marker_->IncrementalMarkingStepForTesting(
StackState::kNoHeapPointers);
}
std::unique_ptr<Marker> marker_;
};
// static
constexpr MarkingConfig EphemeronPairTest::IncrementalPreciseMarkingConfig;
} // namespace
TEST_F(EphemeronPairTest, ValueMarkedWhenKeyIsMarked) {
GCed* key = MakeGarbageCollected<GCed>(GetAllocationHandle());
GCed* value = MakeGarbageCollected<GCed>(GetAllocationHandle());
Persistent<EphemeronHolder> holder =
MakeGarbageCollected<EphemeronHolder>(GetAllocationHandle(), key, value);
HeapObjectHeader::FromObject(key).TryMarkAtomic();
InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get());
FinishMarking();
EXPECT_TRUE(HeapObjectHeader::FromObject(value).IsMarked());
}
TEST_F(EphemeronPairTest, 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::FromObject(key).IsMarked());
EXPECT_FALSE(HeapObjectHeader::FromObject(value).IsMarked());
}
TEST_F(EphemeronPairTest, 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::FromObject(value).IsMarked());
HeapObjectHeader::FromObject(key).TryMarkAtomic();
FinishMarking();
EXPECT_TRUE(HeapObjectHeader::FromObject(value).IsMarked());
}
TEST_F(EphemeronPairTest, TraceEphemeronDispatch) {
GCed* key = MakeGarbageCollected<GCed>(GetAllocationHandle());
GCed* value = MakeGarbageCollected<GCed>(GetAllocationHandle());
Persistent<EphemeronHolder> holder =
MakeGarbageCollected<EphemeronHolder>(GetAllocationHandle(), key, value);
HeapObjectHeader::FromObject(key).TryMarkAtomic();
InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get());
FinishMarking();
EXPECT_TRUE(HeapObjectHeader::FromObject(value).IsMarked());
}
TEST_F(EphemeronPairTest, EmptyValue) {
GCed* key = MakeGarbageCollected<GCed>(GetAllocationHandle());
Persistent<EphemeronHolder> holder = MakeGarbageCollected<EphemeronHolder>(
GetAllocationHandle(), key, nullptr);
HeapObjectHeader::FromObject(key).TryMarkAtomic();
InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get());
FinishMarking();
}
TEST_F(EphemeronPairTest, EmptyKey) {
GCed* value = MakeGarbageCollected<GCed>(GetAllocationHandle());
Persistent<EphemeronHolder> holder = MakeGarbageCollected<EphemeronHolder>(
GetAllocationHandle(), nullptr, value);
InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get());
FinishMarking();
// Key is not alive and value should thus not be held alive.
EXPECT_FALSE(HeapObjectHeader::FromObject(value).IsMarked());
}
using EphemeronPairGCTest = testing::TestWithHeap;
TEST_F(EphemeronPairGCTest, EphemeronPairValueIsCleared) {
GCed* key = MakeGarbageCollected<GCed>(GetAllocationHandle());
GCed* value = MakeGarbageCollected<GCed>(GetAllocationHandle());
Persistent<EphemeronHolder> holder =
MakeGarbageCollected<EphemeronHolder>(GetAllocationHandle(), key, value);
// The precise GC will not find the `key` anywhere and thus clear the
// ephemeron.
PreciseGC();
EXPECT_EQ(nullptr, holder->ephemeron_pair().value.Get());
}
namespace {
class Mixin : public GarbageCollectedMixin {
public:
void Trace(Visitor* v) const override {}
};
class OtherMixin : public GarbageCollectedMixin {
public:
void Trace(Visitor* v) const override {}
};
class GCedWithMixin : public GarbageCollected<GCedWithMixin>,
public OtherMixin,
public Mixin {
public:
void Trace(Visitor* v) const override {
OtherMixin::Trace(v);
Mixin::Trace(v);
}
};
class EphemeronHolderWithMixins
: public GarbageCollected<EphemeronHolderWithMixins> {
public:
EphemeronHolderWithMixins(Mixin* key, Mixin* value)
: ephemeron_pair_(key, value) {}
void Trace(cppgc::Visitor* visitor) const { visitor->Trace(ephemeron_pair_); }
const EphemeronPair<Mixin, Mixin>& ephemeron_pair() const {
return ephemeron_pair_;
}
private:
EphemeronPair<Mixin, Mixin> ephemeron_pair_;
};
} // namespace
TEST_F(EphemeronPairTest, EphemeronPairWithMixinKey) {
GCedWithMixin* key =
MakeGarbageCollected<GCedWithMixin>(GetAllocationHandle());
GCedWithMixin* value =
MakeGarbageCollected<GCedWithMixin>(GetAllocationHandle());
Persistent<EphemeronHolderWithMixins> holder =
MakeGarbageCollected<EphemeronHolderWithMixins>(GetAllocationHandle(),
key, value);
EXPECT_NE(static_cast<void*>(key), holder->ephemeron_pair().key.Get());
EXPECT_NE(static_cast<void*>(value), holder->ephemeron_pair().value.Get());
InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get());
FinishSteps();
EXPECT_FALSE(HeapObjectHeader::FromObject(value).IsMarked());
EXPECT_TRUE(HeapObjectHeader::FromObject(key).TryMarkAtomic());
FinishMarking();
EXPECT_TRUE(HeapObjectHeader::FromObject(value).IsMarked());
}
TEST_F(EphemeronPairTest, EphemeronPairWithEmptyMixinValue) {
GCedWithMixin* key =
MakeGarbageCollected<GCedWithMixin>(GetAllocationHandle());
Persistent<EphemeronHolderWithMixins> holder =
MakeGarbageCollected<EphemeronHolderWithMixins>(GetAllocationHandle(),
key, nullptr);
EXPECT_NE(static_cast<void*>(key), holder->ephemeron_pair().key.Get());
EXPECT_TRUE(HeapObjectHeader::FromObject(key).TryMarkAtomic());
InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get());
FinishSteps();
FinishMarking();
}
namespace {
class KeyWithCallback final : public GarbageCollected<KeyWithCallback> {
public:
template <typename Callback>
explicit KeyWithCallback(Callback callback) {
callback(this);
}
void Trace(Visitor*) const {}
};
class EphemeronHolderForKeyWithCallback final
: public GarbageCollected<EphemeronHolderForKeyWithCallback> {
public:
EphemeronHolderForKeyWithCallback(KeyWithCallback* key, GCed* value)
: ephemeron_pair_(key, value) {}
void Trace(cppgc::Visitor* visitor) const { visitor->Trace(ephemeron_pair_); }
private:
const EphemeronPair<KeyWithCallback, GCed> ephemeron_pair_;
};
} // namespace
TEST_F(EphemeronPairTest, EphemeronPairWithKeyInConstruction) {
GCed* value = MakeGarbageCollected<GCed>(GetAllocationHandle());
Persistent<EphemeronHolderForKeyWithCallback> holder;
InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get());
FinishSteps();
MakeGarbageCollected<KeyWithCallback>(
GetAllocationHandle(), [this, &holder, value](KeyWithCallback* thiz) {
// The test doesn't use conservative stack scanning to retain key to
// avoid retaining value as a side effect.
EXPECT_TRUE(HeapObjectHeader::FromObject(thiz).TryMarkAtomic());
holder = MakeGarbageCollected<EphemeronHolderForKeyWithCallback>(
GetAllocationHandle(), thiz, value);
// Finishing marking at this point will leave an ephemeron pair
// reachable where the key is still in construction. The GC needs to
// mark the value for such pairs as live in the atomic pause as they key
// is considered live.
FinishMarking();
});
EXPECT_TRUE(HeapObjectHeader::FromObject(value).IsMarked());
}
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,208 @@
// 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/explicit-management.h"
#include "include/cppgc/garbage-collected.h"
#include "src/heap/cppgc/globals.h"
#include "src/heap/cppgc/heap-base.h"
#include "src/heap/cppgc/heap-object-header.h"
#include "src/heap/cppgc/heap-space.h"
#include "src/heap/cppgc/page-memory.h"
#include "src/heap/cppgc/sweeper.h"
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
class ExplicitManagementTest : public testing::TestWithHeap {
public:
size_t AllocatedObjectSize() const {
auto* heap = Heap::From(GetHeap());
heap->stats_collector()->NotifySafePointForTesting();
return heap->stats_collector()->allocated_object_size();
}
void ResetLinearAllocationBuffers() const {
return Heap::From(GetHeap())
->object_allocator()
.ResetLinearAllocationBuffers();
}
void TearDown() override {
PreciseGC();
TestWithHeap::TearDown();
}
};
namespace {
class DynamicallySized final : public GarbageCollected<DynamicallySized> {
public:
void Trace(Visitor*) const {}
};
} // namespace
TEST_F(ExplicitManagementTest, FreeRegularObjectToLAB) {
auto* o =
MakeGarbageCollected<DynamicallySized>(GetHeap()->GetAllocationHandle());
const auto& space = NormalPageSpace::From(BasePage::FromPayload(o)->space());
const auto& lab = space.linear_allocation_buffer();
auto& header = HeapObjectHeader::FromObject(o);
const size_t size = header.AllocatedSize();
Address needle = reinterpret_cast<Address>(&header);
// Test checks freeing to LAB.
ASSERT_EQ(lab.start(), header.ObjectEnd());
const size_t lab_size_before_free = lab.size();
const size_t allocated_size_before = AllocatedObjectSize();
subtle::FreeUnreferencedObject(GetHeapHandle(), *o);
EXPECT_EQ(lab.start(), reinterpret_cast<Address>(needle));
EXPECT_EQ(lab_size_before_free + size, lab.size());
// LAB is included in allocated object size, so no change is expected.
EXPECT_EQ(allocated_size_before, AllocatedObjectSize());
EXPECT_FALSE(space.free_list().ContainsForTesting({needle, size}));
}
TEST_F(ExplicitManagementTest, FreeRegularObjectToFreeList) {
auto* o =
MakeGarbageCollected<DynamicallySized>(GetHeap()->GetAllocationHandle());
const auto& space = NormalPageSpace::From(BasePage::FromPayload(o)->space());
const auto& lab = space.linear_allocation_buffer();
auto& header = HeapObjectHeader::FromObject(o);
const size_t size = header.AllocatedSize();
Address needle = reinterpret_cast<Address>(&header);
// Test checks freeing to free list.
ResetLinearAllocationBuffers();
ASSERT_EQ(lab.start(), nullptr);
const size_t allocated_size_before = AllocatedObjectSize();
subtle::FreeUnreferencedObject(GetHeapHandle(), *o);
EXPECT_EQ(lab.start(), nullptr);
EXPECT_EQ(allocated_size_before - size, AllocatedObjectSize());
EXPECT_TRUE(space.free_list().ContainsForTesting({needle, size}));
}
TEST_F(ExplicitManagementTest, FreeLargeObject) {
auto* o = MakeGarbageCollected<DynamicallySized>(
GetHeap()->GetAllocationHandle(),
AdditionalBytes(kLargeObjectSizeThreshold));
const auto* page = BasePage::FromPayload(o);
auto& heap = page->heap();
ASSERT_TRUE(page->is_large());
ConstAddress needle = reinterpret_cast<ConstAddress>(o);
const size_t size = LargePage::From(page)->PayloadSize();
EXPECT_TRUE(heap.page_backend()->Lookup(needle));
const size_t allocated_size_before = AllocatedObjectSize();
subtle::FreeUnreferencedObject(GetHeapHandle(), *o);
EXPECT_FALSE(heap.page_backend()->Lookup(needle));
EXPECT_EQ(allocated_size_before - size, AllocatedObjectSize());
}
TEST_F(ExplicitManagementTest, FreeBailsOutDuringGC) {
const size_t snapshot_before = AllocatedObjectSize();
auto* o =
MakeGarbageCollected<DynamicallySized>(GetHeap()->GetAllocationHandle());
auto& heap = BasePage::FromPayload(o)->heap();
heap.SetInAtomicPauseForTesting(true);
const size_t allocated_size_before = AllocatedObjectSize();
subtle::FreeUnreferencedObject(GetHeapHandle(), *o);
EXPECT_EQ(allocated_size_before, AllocatedObjectSize());
heap.SetInAtomicPauseForTesting(false);
ResetLinearAllocationBuffers();
subtle::FreeUnreferencedObject(GetHeapHandle(), *o);
EXPECT_EQ(snapshot_before, AllocatedObjectSize());
}
TEST_F(ExplicitManagementTest, GrowAtLAB) {
auto* o =
MakeGarbageCollected<DynamicallySized>(GetHeap()->GetAllocationHandle());
auto& header = HeapObjectHeader::FromObject(o);
ASSERT_TRUE(!header.IsLargeObject());
constexpr size_t size_of_o = sizeof(DynamicallySized);
constexpr size_t kFirstDelta = 8;
EXPECT_TRUE(subtle::Resize(*o, AdditionalBytes(kFirstDelta)));
EXPECT_EQ(RoundUp<kAllocationGranularity>(size_of_o + kFirstDelta),
header.ObjectSize());
constexpr size_t kSecondDelta = 9;
EXPECT_TRUE(subtle::Resize(*o, AdditionalBytes(kSecondDelta)));
EXPECT_EQ(RoundUp<kAllocationGranularity>(size_of_o + kSecondDelta),
header.ObjectSize());
// Second round didn't actually grow object because alignment restrictions
// already forced it to be large enough on the first Grow().
EXPECT_EQ(RoundUp<kAllocationGranularity>(size_of_o + kFirstDelta),
RoundUp<kAllocationGranularity>(size_of_o + kSecondDelta));
constexpr size_t kThirdDelta = 16;
EXPECT_TRUE(subtle::Resize(*o, AdditionalBytes(kThirdDelta)));
EXPECT_EQ(RoundUp<kAllocationGranularity>(size_of_o + kThirdDelta),
header.ObjectSize());
}
TEST_F(ExplicitManagementTest, GrowShrinkAtLAB) {
auto* o =
MakeGarbageCollected<DynamicallySized>(GetHeap()->GetAllocationHandle());
auto& header = HeapObjectHeader::FromObject(o);
ASSERT_TRUE(!header.IsLargeObject());
constexpr size_t size_of_o = sizeof(DynamicallySized);
constexpr size_t kDelta = 27;
EXPECT_TRUE(subtle::Resize(*o, AdditionalBytes(kDelta)));
EXPECT_EQ(RoundUp<kAllocationGranularity>(size_of_o + kDelta),
header.ObjectSize());
EXPECT_TRUE(subtle::Resize(*o, AdditionalBytes(0)));
EXPECT_EQ(RoundUp<kAllocationGranularity>(size_of_o), header.ObjectSize());
}
TEST_F(ExplicitManagementTest, ShrinkFreeList) {
auto* o = MakeGarbageCollected<DynamicallySized>(
GetHeap()->GetAllocationHandle(),
AdditionalBytes(ObjectAllocator::kSmallestSpaceSize));
const auto& space = NormalPageSpace::From(BasePage::FromPayload(o)->space());
// Force returning to free list by removing the LAB.
ResetLinearAllocationBuffers();
auto& header = HeapObjectHeader::FromObject(o);
ASSERT_TRUE(!header.IsLargeObject());
constexpr size_t size_of_o = sizeof(DynamicallySized);
EXPECT_TRUE(subtle::Resize(*o, AdditionalBytes(0)));
EXPECT_EQ(RoundUp<kAllocationGranularity>(size_of_o), header.ObjectSize());
EXPECT_TRUE(space.free_list().ContainsForTesting(
{header.ObjectEnd(), ObjectAllocator::kSmallestSpaceSize}));
}
TEST_F(ExplicitManagementTest, ShrinkFreeListBailoutAvoidFragmentation) {
auto* o = MakeGarbageCollected<DynamicallySized>(
GetHeap()->GetAllocationHandle(),
AdditionalBytes(ObjectAllocator::kSmallestSpaceSize - 1));
const auto& space = NormalPageSpace::From(BasePage::FromPayload(o)->space());
// Force returning to free list by removing the LAB.
ResetLinearAllocationBuffers();
auto& header = HeapObjectHeader::FromObject(o);
ASSERT_TRUE(!header.IsLargeObject());
constexpr size_t size_of_o = sizeof(DynamicallySized);
EXPECT_TRUE(subtle::Resize(*o, AdditionalBytes(0)));
EXPECT_EQ(RoundUp<kAllocationGranularity>(
size_of_o + ObjectAllocator::kSmallestSpaceSize - 1),
header.ObjectSize());
EXPECT_FALSE(space.free_list().ContainsForTesting(
{header.ObjectStart() + RoundUp<kAllocationGranularity>(size_of_o),
ObjectAllocator::kSmallestSpaceSize - 1}));
}
TEST_F(ExplicitManagementTest, ResizeBailsOutDuringGC) {
auto* o = MakeGarbageCollected<DynamicallySized>(
GetHeap()->GetAllocationHandle(),
AdditionalBytes(ObjectAllocator::kSmallestSpaceSize - 1));
auto& heap = BasePage::FromPayload(o)->heap();
heap.SetInAtomicPauseForTesting(true);
const size_t allocated_size_before = AllocatedObjectSize();
// Grow:
EXPECT_FALSE(
subtle::Resize(*o, AdditionalBytes(ObjectAllocator::kSmallestSpaceSize)));
// Shrink:
EXPECT_FALSE(subtle::Resize(*o, AdditionalBytes(0)));
EXPECT_EQ(allocated_size_before, AllocatedObjectSize());
heap.SetInAtomicPauseForTesting(false);
}
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,120 @@
// 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/finalizer-trait.h"
#include <type_traits>
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace {
// Trivially destructible types.
class TypeWithoutDestructor final {};
class TypeWithPrimitive final {
public:
int foo = 0;
};
class InvokeCounter {
public:
static size_t kCallcount;
static void Reset() { kCallcount = 0; }
static void Invoke() { kCallcount++; }
};
size_t InvokeCounter::kCallcount = 0;
// Regular C++ use cases.
class TypeWithDestructor final : public InvokeCounter {
public:
~TypeWithDestructor() { Invoke(); }
};
class TypeWithVirtualDestructorBase {
public:
virtual ~TypeWithVirtualDestructorBase() = default;
};
class TypeWithVirtualDestructorChild final
: public TypeWithVirtualDestructorBase,
public InvokeCounter {
public:
~TypeWithVirtualDestructorChild() final { Invoke(); }
};
// Manual dispatch to avoid vtables.
class TypeWithCustomFinalizationMethod final : public InvokeCounter {
public:
void FinalizeGarbageCollectedObject() { Invoke(); }
};
class TypeWithCustomFinalizationMethodAtBase {
public:
void FinalizeGarbageCollectedObject();
};
class TypeWithCustomFinalizationMethodAtBaseChild
: public TypeWithCustomFinalizationMethodAtBase,
public InvokeCounter {
public:
~TypeWithCustomFinalizationMethodAtBaseChild() { Invoke(); }
};
void TypeWithCustomFinalizationMethodAtBase::FinalizeGarbageCollectedObject() {
// The test knows that base is only inherited by a single child. In practice
// users can maintain a map of valid types in already existing storage.
static_cast<TypeWithCustomFinalizationMethodAtBaseChild*>(this)
->~TypeWithCustomFinalizationMethodAtBaseChild();
}
template <typename Type>
void ExpectFinalizerIsInvoked(Type* object) {
InvokeCounter::Reset();
EXPECT_NE(nullptr, FinalizerTrait<Type>::kCallback);
FinalizerTrait<Type>::kCallback(object);
EXPECT_EQ(1u, InvokeCounter::kCallcount);
operator delete(object);
}
} // namespace
TEST(FinalizerTrait, TypeWithoutDestructorHasNoFinalizer) {
static_assert(std::is_trivially_destructible<TypeWithoutDestructor>::value,
"trivially destructible");
EXPECT_EQ(nullptr, FinalizerTrait<TypeWithoutDestructor>::kCallback);
}
TEST(FinalizerTrait, TypeWithPrimitiveHasNoFinalizer) {
static_assert(std::is_trivially_destructible<TypeWithPrimitive>::value,
"trivially destructible");
EXPECT_EQ(nullptr, FinalizerTrait<TypeWithPrimitive>::kCallback);
}
TEST(FinalizerTrait, FinalizerForTypeWithDestructor) {
ExpectFinalizerIsInvoked(new TypeWithDestructor());
}
TEST(FinalizerTrait, FinalizerForTypeWithVirtualBaseDtor) {
TypeWithVirtualDestructorBase* base = new TypeWithVirtualDestructorChild();
ExpectFinalizerIsInvoked(base);
}
TEST(FinalizerTrait, FinalizerForCustomFinalizationMethod) {
ExpectFinalizerIsInvoked(new TypeWithCustomFinalizationMethod());
}
TEST(FinalizerTrait, FinalizerForCustomFinalizationMethodInBase) {
TypeWithCustomFinalizationMethodAtBase* base =
new TypeWithCustomFinalizationMethodAtBaseChild();
ExpectFinalizerIsInvoked(base);
}
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,196 @@
// 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/free-list.h"
#include <memory>
#include <numeric>
#include <vector>
#include "src/base/bits.h"
#include "src/heap/cppgc/globals.h"
#include "src/heap/cppgc/heap-object-header.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace {
class Block {
public:
Block() = default;
explicit Block(size_t size) : address_(calloc(1, size)), size_(size) {}
Block(Block&& other) V8_NOEXCEPT : address_(other.address_),
size_(other.size_) {
other.address_ = nullptr;
other.size_ = 0;
}
Block& operator=(Block&& other) V8_NOEXCEPT {
address_ = other.address_;
size_ = other.size_;
other.address_ = nullptr;
other.size_ = 0;
return *this;
}
~Block() { free(address_); }
void* Address() const { return address_; }
size_t Size() const { return size_; }
private:
void* address_ = nullptr;
size_t size_ = 0;
};
std::vector<Block> CreateEntries() {
static constexpr size_t kFreeListEntrySizeLog2 =
v8::base::bits::WhichPowerOfTwo(kFreeListEntrySize);
std::vector<Block> vector;
vector.reserve(kPageSizeLog2);
for (size_t i = kFreeListEntrySizeLog2; i < kPageSizeLog2; ++i) {
vector.emplace_back(static_cast<size_t>(1u) << i);
}
return vector;
}
FreeList CreatePopulatedFreeList(const std::vector<Block>& blocks) {
FreeList list;
for (const auto& block : blocks) {
list.Add({block.Address(), block.Size()});
}
return list;
}
} // namespace
TEST(FreeListTest, Empty) {
FreeList list;
EXPECT_TRUE(list.IsEmpty());
EXPECT_EQ(0u, list.Size());
auto block = list.Allocate(16);
EXPECT_EQ(nullptr, block.address);
EXPECT_EQ(0u, block.size);
}
TEST(FreeListTest, Add) {
auto blocks = CreateEntries();
FreeList list = CreatePopulatedFreeList(blocks);
EXPECT_FALSE(list.IsEmpty());
const size_t allocated_size = std::accumulate(
blocks.cbegin(), blocks.cend(), 0u,
[](size_t acc, const Block& b) { return acc + b.Size(); });
EXPECT_EQ(allocated_size, list.Size());
}
TEST(FreeListTest, AddWasted) {
FreeList list;
alignas(HeapObjectHeader) uint8_t buffer[sizeof(HeapObjectHeader)];
list.Add({buffer, sizeof(buffer)});
EXPECT_EQ(0u, list.Size());
EXPECT_TRUE(list.IsEmpty());
}
TEST(FreeListTest, Clear) {
auto blocks = CreateEntries();
FreeList list = CreatePopulatedFreeList(blocks);
list.Clear();
EXPECT_EQ(0u, list.Size());
EXPECT_TRUE(list.IsEmpty());
}
TEST(FreeListTest, Move) {
{
auto blocks = CreateEntries();
FreeList list1 = CreatePopulatedFreeList(blocks);
const size_t expected_size = list1.Size();
FreeList list2 = std::move(list1);
EXPECT_EQ(expected_size, list2.Size());
EXPECT_FALSE(list2.IsEmpty());
EXPECT_EQ(0u, list1.Size());
EXPECT_TRUE(list1.IsEmpty());
}
{
auto blocks1 = CreateEntries();
FreeList list1 = CreatePopulatedFreeList(blocks1);
const size_t expected_size = list1.Size();
auto blocks2 = CreateEntries();
FreeList list2 = CreatePopulatedFreeList(blocks2);
list2 = std::move(list1);
EXPECT_EQ(expected_size, list2.Size());
EXPECT_FALSE(list2.IsEmpty());
EXPECT_EQ(0u, list1.Size());
EXPECT_TRUE(list1.IsEmpty());
}
}
TEST(FreeListTest, Append) {
auto blocks1 = CreateEntries();
FreeList list1 = CreatePopulatedFreeList(blocks1);
const size_t list1_size = list1.Size();
auto blocks2 = CreateEntries();
FreeList list2 = CreatePopulatedFreeList(blocks2);
const size_t list2_size = list1.Size();
list2.Append(std::move(list1));
EXPECT_EQ(list1_size + list2_size, list2.Size());
EXPECT_FALSE(list2.IsEmpty());
EXPECT_EQ(0u, list1.Size());
EXPECT_TRUE(list1.IsEmpty());
}
#ifdef DEBUG
TEST(FreeListTest, AppendSelf) {
auto blocks = CreateEntries();
FreeList list = CreatePopulatedFreeList(blocks);
// Appending a free list to itself should fail in debug builds.
EXPECT_DEATH_IF_SUPPORTED({ list.Append(std::move(list)); }, "");
}
#endif
TEST(FreeListTest, Contains) {
auto blocks = CreateEntries();
FreeList list = CreatePopulatedFreeList(blocks);
for (const auto& block : blocks) {
EXPECT_TRUE(list.ContainsForTesting({block.Address(), block.Size()}));
}
}
TEST(FreeListTest, Allocate) {
static constexpr size_t kFreeListEntrySizeLog2 =
v8::base::bits::WhichPowerOfTwo(kFreeListEntrySize);
std::vector<Block> blocks;
blocks.reserve(kPageSizeLog2);
for (size_t i = kFreeListEntrySizeLog2; i < kPageSizeLog2; ++i) {
blocks.emplace_back(static_cast<size_t>(1u) << i);
}
FreeList list = CreatePopulatedFreeList(blocks);
// Try allocate from the biggest block.
for (auto it = blocks.rbegin(); it < blocks.rend(); ++it) {
const auto result = list.Allocate(it->Size());
EXPECT_EQ(it->Address(), result.address);
EXPECT_EQ(it->Size(), result.size);
}
EXPECT_EQ(0u, list.Size());
EXPECT_TRUE(list.IsEmpty());
// Check that allocation fails for empty list:
const auto empty_block = list.Allocate(8);
EXPECT_EQ(nullptr, empty_block.address);
EXPECT_EQ(0u, empty_block.size);
}
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,264 @@
// 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/garbage-collected.h"
#include "include/cppgc/allocation.h"
#include "include/cppgc/type-traits.h"
#include "src/base/platform/mutex.h"
#include "src/heap/cppgc/heap-object-header.h"
#include "src/heap/cppgc/heap.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:
virtual void Trace(Visitor*) const {}
};
class NotGCed {};
class Mixin : public GarbageCollectedMixin {};
class GCedWithMixin : public GarbageCollected<GCedWithMixin>, public Mixin {};
class OtherMixin : public GarbageCollectedMixin {};
class MergedMixins : public Mixin, public OtherMixin {
public:
void Trace(cppgc::Visitor* visitor) const override {
Mixin::Trace(visitor);
OtherMixin::Trace(visitor);
}
};
class GCWithMergedMixins : public GCed, public MergedMixins {
public:
void Trace(cppgc::Visitor* visitor) const override {
GCed::Trace(visitor);
MergedMixins::Trace(visitor);
}
};
class GarbageCollectedTestWithHeap
: public testing::TestSupportingAllocationOnly {};
} // namespace
TEST(GarbageCollectedTest, GarbageCollectedTrait) {
static_assert(!IsGarbageCollectedTypeV<int>);
static_assert(!IsGarbageCollectedTypeV<NotGCed>);
static_assert(IsGarbageCollectedTypeV<GCed>);
static_assert(!IsGarbageCollectedTypeV<Mixin>);
static_assert(IsGarbageCollectedTypeV<GCedWithMixin>);
static_assert(!IsGarbageCollectedTypeV<MergedMixins>);
static_assert(IsGarbageCollectedTypeV<GCWithMergedMixins>);
}
TEST(GarbageCollectedTest, GarbageCollectedMixinTrait) {
static_assert(!IsGarbageCollectedMixinTypeV<int>);
static_assert(!IsGarbageCollectedMixinTypeV<GCed>);
static_assert(!IsGarbageCollectedMixinTypeV<NotGCed>);
static_assert(IsGarbageCollectedMixinTypeV<Mixin>);
static_assert(!IsGarbageCollectedMixinTypeV<GCedWithMixin>);
static_assert(IsGarbageCollectedMixinTypeV<MergedMixins>);
static_assert(!IsGarbageCollectedMixinTypeV<GCWithMergedMixins>);
}
TEST(GarbageCollectedTest, GarbageCollectedOrMixinTrait) {
static_assert(!IsGarbageCollectedOrMixinTypeV<int>);
static_assert(IsGarbageCollectedOrMixinTypeV<GCed>);
static_assert(!IsGarbageCollectedOrMixinTypeV<NotGCed>);
static_assert(IsGarbageCollectedOrMixinTypeV<Mixin>);
static_assert(IsGarbageCollectedOrMixinTypeV<GCedWithMixin>);
static_assert(IsGarbageCollectedOrMixinTypeV<MergedMixins>);
static_assert(IsGarbageCollectedOrMixinTypeV<GCWithMergedMixins>);
}
TEST(GarbageCollectedTest, GarbageCollectedWithMixinTrait) {
static_assert(!IsGarbageCollectedWithMixinTypeV<int>);
static_assert(!IsGarbageCollectedWithMixinTypeV<GCed>);
static_assert(!IsGarbageCollectedWithMixinTypeV<NotGCed>);
static_assert(!IsGarbageCollectedWithMixinTypeV<Mixin>);
static_assert(IsGarbageCollectedWithMixinTypeV<GCedWithMixin>);
static_assert(!IsGarbageCollectedWithMixinTypeV<MergedMixins>);
static_assert(IsGarbageCollectedWithMixinTypeV<GCWithMergedMixins>);
}
namespace {
class ForwardDeclaredType;
} // namespace
TEST(GarbageCollectedTest, CompleteTypeTrait) {
static_assert(IsCompleteV<GCed>);
static_assert(!IsCompleteV<ForwardDeclaredType>);
}
TEST_F(GarbageCollectedTestWithHeap, GetObjectStartReturnsCurrentAddress) {
GCed* gced = MakeGarbageCollected<GCed>(GetAllocationHandle());
GCedWithMixin* gced_with_mixin =
MakeGarbageCollected<GCedWithMixin>(GetAllocationHandle());
const void* base_object_payload = TraceTrait<Mixin>::GetTraceDescriptor(
static_cast<Mixin*>(gced_with_mixin))
.base_object_payload;
EXPECT_EQ(gced_with_mixin, base_object_payload);
EXPECT_NE(gced, base_object_payload);
}
namespace {
class GCedWithPostConstructionCallback final : public GCed {
public:
static size_t cb_callcount;
GCedWithPostConstructionCallback() { cb_callcount = 0; }
};
size_t GCedWithPostConstructionCallback::cb_callcount;
class MixinWithPostConstructionCallback {
public:
static size_t cb_callcount;
MixinWithPostConstructionCallback() { cb_callcount = 0; }
using MarkerForMixinWithPostConstructionCallback = int;
};
size_t MixinWithPostConstructionCallback::cb_callcount;
class GCedWithMixinWithPostConstructionCallback final
: public GCed,
public MixinWithPostConstructionCallback {};
} // namespace
} // namespace internal
template <>
struct PostConstructionCallbackTrait<
internal::GCedWithPostConstructionCallback> {
static void Call(internal::GCedWithPostConstructionCallback* object) {
EXPECT_FALSE(
internal::HeapObjectHeader::FromObject(object).IsInConstruction());
internal::GCedWithPostConstructionCallback::cb_callcount++;
}
};
template <typename T>
struct PostConstructionCallbackTrait<
T, std::void_t<typename T::MarkerForMixinWithPostConstructionCallback>> {
// The parameter could just be T*.
static void Call(
internal::GCedWithMixinWithPostConstructionCallback* object) {
EXPECT_FALSE(
internal::HeapObjectHeader::FromObject(object).IsInConstruction());
internal::GCedWithMixinWithPostConstructionCallback::cb_callcount++;
}
};
namespace internal {
TEST_F(GarbageCollectedTestWithHeap, PostConstructionCallback) {
EXPECT_EQ(0u, GCedWithPostConstructionCallback::cb_callcount);
MakeGarbageCollected<GCedWithPostConstructionCallback>(GetAllocationHandle());
EXPECT_EQ(1u, GCedWithPostConstructionCallback::cb_callcount);
}
TEST_F(GarbageCollectedTestWithHeap, PostConstructionCallbackForMixin) {
EXPECT_EQ(0u, MixinWithPostConstructionCallback::cb_callcount);
MakeGarbageCollected<GCedWithMixinWithPostConstructionCallback>(
GetAllocationHandle());
EXPECT_EQ(1u, MixinWithPostConstructionCallback::cb_callcount);
}
namespace {
int GetDummyValue() {
static v8::base::Mutex mutex;
static int ret = 43;
// Global lock access to avoid reordering.
v8::base::MutexGuard guard(&mutex);
return ret;
}
class CheckObjectInConstructionBeforeInitializerList final
: public GarbageCollected<CheckObjectInConstructionBeforeInitializerList> {
public:
CheckObjectInConstructionBeforeInitializerList()
: in_construction_before_initializer_list_(
HeapObjectHeader::FromObject(this).IsInConstruction()),
unused_int_(GetDummyValue()) {
EXPECT_TRUE(in_construction_before_initializer_list_);
EXPECT_TRUE(HeapObjectHeader::FromObject(this).IsInConstruction());
}
void Trace(Visitor*) const {}
private:
bool in_construction_before_initializer_list_;
int unused_int_;
};
class CheckMixinInConstructionBeforeInitializerList
: public GarbageCollectedMixin {
public:
explicit CheckMixinInConstructionBeforeInitializerList(void* payload_start)
: in_construction_before_initializer_list_(
HeapObjectHeader::FromObject(payload_start).IsInConstruction()),
unused_int_(GetDummyValue()) {
EXPECT_TRUE(in_construction_before_initializer_list_);
EXPECT_TRUE(HeapObjectHeader::FromObject(payload_start).IsInConstruction());
}
void Trace(Visitor*) const override {}
private:
bool in_construction_before_initializer_list_;
int unused_int_;
};
class UnmanagedMixinForcingVTable {
protected:
virtual void ForceVTable() {}
};
class CheckGCedWithMixinInConstructionBeforeInitializerList
: public GarbageCollected<
CheckGCedWithMixinInConstructionBeforeInitializerList>,
public UnmanagedMixinForcingVTable,
public CheckMixinInConstructionBeforeInitializerList {
public:
CheckGCedWithMixinInConstructionBeforeInitializerList()
: CheckMixinInConstructionBeforeInitializerList(this) {
// Ensure that compiler indeed generated an inner object.
CHECK_NE(
this,
static_cast<void*>(
static_cast<CheckMixinInConstructionBeforeInitializerList*>(this)));
}
};
} // namespace
TEST_F(GarbageCollectedTestWithHeap, GarbageCollectedInConstructionDuringCtor) {
MakeGarbageCollected<CheckObjectInConstructionBeforeInitializerList>(
GetAllocationHandle());
}
TEST_F(GarbageCollectedTestWithHeap,
GarbageCollectedMixinInConstructionDuringCtor) {
MakeGarbageCollected<CheckGCedWithMixinInConstructionBeforeInitializerList>(
GetAllocationHandle());
}
namespace {
struct MixinA : GarbageCollectedMixin {};
struct MixinB : GarbageCollectedMixin {};
struct GCed1 : GarbageCollected<GCed1>, MixinA, MixinB {};
struct GCed2 : MixinA, MixinB {};
static_assert(
sizeof(GCed1) == sizeof(GCed2),
"Check that empty base optimization always works for GarbageCollected");
} // namespace
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,317 @@
// 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/gc-info.h"
#include <type_traits>
#include "include/cppgc/platform.h"
#include "src/base/page-allocator.h"
#include "src/base/platform/platform.h"
#include "src/heap/cppgc/gc-info-table.h"
#include "src/heap/cppgc/platform.h"
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace {
constexpr GCInfo GetEmptyGCInfo() { return {nullptr, nullptr, nullptr}; }
class GCInfoTableTest : public ::testing::Test {
public:
GCInfoTableTest()
: table_(std::make_unique<GCInfoTable>(page_allocator_, oom_handler_)) {}
GCInfoIndex RegisterNewGCInfoForTesting(const GCInfo& info) {
// Unused registered index will result in registering a new index.
std::atomic<GCInfoIndex> registered_index{0};
return table().RegisterNewGCInfo(registered_index, info);
}
GCInfoTable& table() { return *table_; }
const GCInfoTable& table() const { return *table_; }
private:
v8::base::PageAllocator page_allocator_;
FatalOutOfMemoryHandler oom_handler_;
std::unique_ptr<GCInfoTable> table_;
};
using GCInfoTableDeathTest = GCInfoTableTest;
} // namespace
TEST_F(GCInfoTableTest, InitialEmpty) {
EXPECT_EQ(GCInfoTable::kMinIndex, table().NumberOfGCInfos());
}
TEST_F(GCInfoTableTest, ResizeToMaxIndex) {
GCInfo info = GetEmptyGCInfo();
for (GCInfoIndex i = GCInfoTable::kMinIndex; i < GCInfoTable::kMaxIndex;
i++) {
GCInfoIndex index = RegisterNewGCInfoForTesting(info);
EXPECT_EQ(i, index);
}
}
TEST_F(GCInfoTableDeathTest, MoreThanMaxIndexInfos) {
GCInfo info = GetEmptyGCInfo();
// Create GCInfoTable::kMaxIndex entries.
for (GCInfoIndex i = GCInfoTable::kMinIndex; i < GCInfoTable::kMaxIndex;
i++) {
RegisterNewGCInfoForTesting(info);
}
EXPECT_DEATH_IF_SUPPORTED(RegisterNewGCInfoForTesting(info), "");
}
TEST_F(GCInfoTableDeathTest, OldTableAreaIsReadOnly) {
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
// pages. In this case, nothing can be comitted as read-only.
if (limit == GCInfoTable::kMaxIndex) {
return;
}
for (GCInfoIndex i = GCInfoTable::kMinIndex; i < limit; i++) {
RegisterNewGCInfoForTesting(info);
}
EXPECT_EQ(limit, table().LimitForTesting());
RegisterNewGCInfoForTesting(info);
EXPECT_NE(limit, table().LimitForTesting());
// Old area is now read-only.
auto& first_slot = table().TableSlotForTesting(GCInfoTable::kMinIndex);
EXPECT_DEATH_IF_SUPPORTED(first_slot.finalize = nullptr, "");
}
namespace {
class ThreadRegisteringGCInfoObjects final : public v8::base::Thread {
public:
ThreadRegisteringGCInfoObjects(GCInfoTableTest* test,
GCInfoIndex num_registrations)
: v8::base::Thread(Options("Thread registering GCInfo objects.")),
test_(test),
num_registrations_(num_registrations) {}
void Run() final {
GCInfo info = GetEmptyGCInfo();
for (GCInfoIndex i = 0; i < num_registrations_; i++) {
test_->RegisterNewGCInfoForTesting(info);
}
}
private:
GCInfoTableTest* test_;
GCInfoIndex num_registrations_;
};
} // namespace
TEST_F(GCInfoTableTest, MultiThreadedResizeToMaxIndex) {
constexpr size_t num_threads = 4;
constexpr size_t main_thread_initialized = 2;
constexpr size_t gc_infos_to_register =
(GCInfoTable::kMaxIndex - 1) -
(GCInfoTable::kMinIndex + main_thread_initialized);
static_assert(gc_infos_to_register % num_threads == 0,
"must sum up to kMaxIndex");
constexpr size_t gc_infos_per_thread = gc_infos_to_register / num_threads;
GCInfo info = GetEmptyGCInfo();
for (size_t i = 0; i < main_thread_initialized; i++) {
RegisterNewGCInfoForTesting(info);
}
v8::base::Thread* threads[num_threads];
for (size_t i = 0; i < num_threads; i++) {
threads[i] = new ThreadRegisteringGCInfoObjects(this, gc_infos_per_thread);
}
for (size_t i = 0; i < num_threads; i++) {
CHECK(threads[i]->Start());
}
for (size_t i = 0; i < num_threads; i++) {
threads[i]->Join();
delete threads[i];
}
}
// Tests using the global table and GCInfoTrait.
namespace {
class GCInfoTraitTest : public testing::TestWithPlatform {};
class BasicType final {
public:
void Trace(Visitor*) const {}
};
class OtherBasicType final {
public:
void Trace(Visitor*) const {}
};
} // namespace
TEST_F(GCInfoTraitTest, IndexInBounds) {
const GCInfoIndex index = GCInfoTrait<BasicType>::Index();
EXPECT_GT(GCInfoTable::kMaxIndex, index);
EXPECT_LE(GCInfoTable::kMinIndex, index);
}
TEST_F(GCInfoTraitTest, TraitReturnsSameIndexForSameType) {
const GCInfoIndex index1 = GCInfoTrait<BasicType>::Index();
const GCInfoIndex index2 = GCInfoTrait<BasicType>::Index();
EXPECT_EQ(index1, index2);
}
TEST_F(GCInfoTraitTest, TraitReturnsDifferentIndexForDifferentTypes) {
const GCInfoIndex index1 = GCInfoTrait<BasicType>::Index();
const GCInfoIndex index2 = GCInfoTrait<OtherBasicType>::Index();
EXPECT_NE(index1, index2);
}
namespace {
struct Dummy {};
class BaseWithVirtualDestructor
: public GarbageCollected<BaseWithVirtualDestructor> {
public:
virtual ~BaseWithVirtualDestructor() = default;
void Trace(Visitor*) const {}
private:
std::unique_ptr<Dummy> non_trivially_destructible_;
};
class ChildOfBaseWithVirtualDestructor : public BaseWithVirtualDestructor {
public:
~ChildOfBaseWithVirtualDestructor() override = default;
};
static_assert(std::has_virtual_destructor<BaseWithVirtualDestructor>::value,
"Must have virtual destructor.");
static_assert(!std::is_trivially_destructible<BaseWithVirtualDestructor>::value,
"Must not be trivially destructible");
#ifdef CPPGC_SUPPORTS_OBJECT_NAMES
static_assert(std::is_same<typename internal::GCInfoFolding<
ChildOfBaseWithVirtualDestructor,
ChildOfBaseWithVirtualDestructor::
ParentMostGarbageCollectedType>::ResultType,
ChildOfBaseWithVirtualDestructor>::value,
"No folding to preserve object names");
#else // !CPPGC_SUPPORTS_OBJECT_NAMES
static_assert(std::is_same<typename internal::GCInfoFolding<
ChildOfBaseWithVirtualDestructor,
ChildOfBaseWithVirtualDestructor::
ParentMostGarbageCollectedType>::ResultType,
BaseWithVirtualDestructor>::value,
"Must fold into base as base has virtual destructor.");
#endif // !CPPGC_SUPPORTS_OBJECT_NAMES
class TriviallyDestructibleBase
: public GarbageCollected<TriviallyDestructibleBase> {
public:
virtual void Trace(Visitor*) const {}
};
class ChildOfTriviallyDestructibleBase : public TriviallyDestructibleBase {};
static_assert(!std::has_virtual_destructor<TriviallyDestructibleBase>::value,
"Must not have virtual destructor.");
static_assert(std::is_trivially_destructible<TriviallyDestructibleBase>::value,
"Must be trivially destructible");
#ifdef CPPGC_SUPPORTS_OBJECT_NAMES
static_assert(std::is_same<typename internal::GCInfoFolding<
ChildOfTriviallyDestructibleBase,
ChildOfTriviallyDestructibleBase::
ParentMostGarbageCollectedType>::ResultType,
ChildOfTriviallyDestructibleBase>::value,
"No folding to preserve object names");
#else // !CPPGC_SUPPORTS_OBJECT_NAMES
static_assert(std::is_same<typename internal::GCInfoFolding<
ChildOfTriviallyDestructibleBase,
ChildOfTriviallyDestructibleBase::
ParentMostGarbageCollectedType>::ResultType,
TriviallyDestructibleBase>::value,
"Must fold into base as both are trivially destructible.");
#endif // !CPPGC_SUPPORTS_OBJECT_NAMES
class TypeWithCustomFinalizationMethodAtBase
: public GarbageCollected<TypeWithCustomFinalizationMethodAtBase> {
public:
explicit TypeWithCustomFinalizationMethodAtBase(bool is_child = false)
: is_child_(is_child) {}
void FinalizeGarbageCollectedObject();
void Trace(Visitor* v) const;
void TraceAfterDispatch(Visitor* v) const {}
protected:
const bool is_child_;
private:
std::unique_ptr<Dummy> non_trivially_destructible_;
};
class ChildOfTypeWithCustomFinalizationMethodAtBase
: public TypeWithCustomFinalizationMethodAtBase {
public:
ChildOfTypeWithCustomFinalizationMethodAtBase()
: TypeWithCustomFinalizationMethodAtBase(true) {}
void TraceAfterDispatch(Visitor* v) const {
TypeWithCustomFinalizationMethodAtBase::TraceAfterDispatch(v);
}
};
void TypeWithCustomFinalizationMethodAtBase::FinalizeGarbageCollectedObject() {
if (is_child_) {
static_cast<const ChildOfTypeWithCustomFinalizationMethodAtBase*>(this)
->~ChildOfTypeWithCustomFinalizationMethodAtBase();
} else {
this->~TypeWithCustomFinalizationMethodAtBase();
}
}
void TypeWithCustomFinalizationMethodAtBase::Trace(Visitor* v) const {
if (is_child_) {
static_cast<const ChildOfTypeWithCustomFinalizationMethodAtBase*>(this)
->TraceAfterDispatch(v);
} else {
TraceAfterDispatch(v);
}
}
static_assert(
!std::has_virtual_destructor<TypeWithCustomFinalizationMethodAtBase>::value,
"Must not have virtual destructor.");
static_assert(!std::is_trivially_destructible<
TypeWithCustomFinalizationMethodAtBase>::value,
"Must not be trivially destructible");
#ifdef CPPGC_SUPPORTS_OBJECT_NAMES
static_assert(
std::is_same<typename internal::GCInfoFolding<
ChildOfTypeWithCustomFinalizationMethodAtBase,
ChildOfTypeWithCustomFinalizationMethodAtBase::
ParentMostGarbageCollectedType>::ResultType,
ChildOfTypeWithCustomFinalizationMethodAtBase>::value,
"No folding to preserve object names");
#else // !CPPGC_SUPPORTS_OBJECT_NAMES
static_assert(std::is_same<typename internal::GCInfoFolding<
ChildOfTypeWithCustomFinalizationMethodAtBase,
ChildOfTypeWithCustomFinalizationMethodAtBase::
ParentMostGarbageCollectedType>::ResultType,
TypeWithCustomFinalizationMethodAtBase>::value,
"Must fold into base as base has custom finalizer dispatch.");
#endif // !CPPGC_SUPPORTS_OBJECT_NAMES
} // namespace
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,152 @@
// 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/gc-invoker.h"
#include <optional>
#include "include/cppgc/platform.h"
#include "src/heap/cppgc/heap.h"
#include "test/unittests/heap/cppgc/test-platform.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc::internal {
namespace {
class MockGarbageCollector : public GarbageCollector {
public:
MOCK_METHOD(void, CollectGarbage, (GCConfig), (override));
MOCK_METHOD(void, StartIncrementalGarbageCollection, (GCConfig), (override));
MOCK_METHOD(size_t, epoch, (), (const, override));
MOCK_METHOD(std::optional<EmbedderStackState>, overridden_stack_state, (),
(const, override));
MOCK_METHOD(void, set_override_stack_state, (EmbedderStackState), (override));
MOCK_METHOD(void, clear_overridden_stack_state, (), (override));
#ifdef V8_ENABLE_ALLOCATION_TIMEOUT
MOCK_METHOD(std::optional<int>, UpdateAllocationTimeout, (), (override));
#endif // V8_ENABLE_ALLOCATION_TIMEOUT
};
class MockTaskRunner : public cppgc::TaskRunner {
public:
MOCK_METHOD(void, PostTaskImpl,
(std::unique_ptr<cppgc::Task>, const SourceLocation&),
(override));
MOCK_METHOD(void, PostNonNestableTaskImpl,
(std::unique_ptr<cppgc::Task>, const SourceLocation&),
(override));
MOCK_METHOD(void, PostDelayedTaskImpl,
(std::unique_ptr<cppgc::Task>, double, const SourceLocation&),
(override));
MOCK_METHOD(void, PostNonNestableDelayedTaskImpl,
(std::unique_ptr<cppgc::Task>, double, const SourceLocation&),
(override));
MOCK_METHOD(void, PostIdleTaskImpl,
(std::unique_ptr<cppgc::IdleTask>, const SourceLocation&),
(override));
bool IdleTasksEnabled() override { return true; }
bool NonNestableTasksEnabled() const override { return true; }
bool NonNestableDelayedTasksEnabled() const override { return true; }
};
class MockPlatform : public cppgc::Platform {
public:
explicit MockPlatform(std::shared_ptr<TaskRunner> runner)
: runner_(std::move(runner)),
tracing_controller_(std::make_unique<TracingController>()) {}
PageAllocator* GetPageAllocator() override { return nullptr; }
double MonotonicallyIncreasingTime() override { return 0.0; }
std::shared_ptr<TaskRunner> GetForegroundTaskRunner(
TaskPriority priority) override {
return runner_;
}
TracingController* GetTracingController() override {
return tracing_controller_.get();
}
private:
std::shared_ptr<TaskRunner> runner_;
std::unique_ptr<TracingController> tracing_controller_;
};
} // namespace
TEST(GCInvokerTest, PrecideGCIsInvokedSynchronously) {
MockPlatform platform(nullptr);
MockGarbageCollector gc;
GCInvoker invoker(&gc, &platform,
cppgc::Heap::StackSupport::kNoConservativeStackScan);
EXPECT_CALL(gc, CollectGarbage(::testing::Field(
&GCConfig::stack_state, StackState::kNoHeapPointers)));
invoker.CollectGarbage(GCConfig::PreciseAtomicConfig());
}
TEST(GCInvokerTest, ConservativeGCIsInvokedSynchronouslyWhenSupported) {
MockPlatform platform(nullptr);
MockGarbageCollector gc;
GCInvoker invoker(&gc, &platform,
cppgc::Heap::StackSupport::kSupportsConservativeStackScan);
EXPECT_CALL(
gc, CollectGarbage(::testing::Field(
&GCConfig::stack_state, StackState::kMayContainHeapPointers)));
invoker.CollectGarbage(GCConfig::ConservativeAtomicConfig());
}
TEST(GCInvokerTest, ConservativeGCIsScheduledAsPreciseGCViaPlatform) {
std::shared_ptr<cppgc::TaskRunner> runner =
std::shared_ptr<cppgc::TaskRunner>(new MockTaskRunner());
MockPlatform platform(runner);
MockGarbageCollector gc;
GCInvoker invoker(&gc, &platform,
cppgc::Heap::StackSupport::kNoConservativeStackScan);
EXPECT_CALL(gc, epoch).WillOnce(::testing::Return(0));
EXPECT_CALL(*static_cast<MockTaskRunner*>(runner.get()),
PostNonNestableTaskImpl(::testing::_, ::testing::_));
invoker.CollectGarbage(GCConfig::ConservativeAtomicConfig());
}
TEST(GCInvokerTest, ConservativeGCIsInvokedAsPreciseGCViaPlatform) {
testing::TestPlatform platform;
MockGarbageCollector gc;
GCInvoker invoker(&gc, &platform,
cppgc::Heap::StackSupport::kNoConservativeStackScan);
EXPECT_CALL(gc, epoch).WillRepeatedly(::testing::Return(0));
EXPECT_CALL(gc, CollectGarbage);
invoker.CollectGarbage(GCConfig::ConservativeAtomicConfig());
platform.RunAllForegroundTasks();
}
TEST(GCInvokerTest, IncrementalGCIsStarted) {
// Since StartIncrementalGarbageCollection doesn't scan the stack, support for
// conservative stack scanning should not matter.
MockPlatform platform(nullptr);
MockGarbageCollector gc;
// Conservative stack scanning supported.
GCInvoker invoker_with_support(
&gc, &platform,
cppgc::Heap::StackSupport::kSupportsConservativeStackScan);
EXPECT_CALL(
gc, StartIncrementalGarbageCollection(::testing::Field(
&GCConfig::stack_state, StackState::kMayContainHeapPointers)));
invoker_with_support.StartIncrementalGarbageCollection(
GCConfig::ConservativeIncrementalConfig());
// Conservative stack scanning *not* supported.
GCInvoker invoker_without_support(
&gc, &platform, cppgc::Heap::StackSupport::kNoConservativeStackScan);
EXPECT_CALL(gc,
StartIncrementalGarbageCollection(::testing::Field(
&GCConfig::stack_state, StackState::kMayContainHeapPointers)))
.Times(0);
invoker_without_support.StartIncrementalGarbageCollection(
GCConfig::ConservativeIncrementalConfig());
}
} // namespace cppgc::internal

View File

@ -0,0 +1,186 @@
// 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-growing.h"
#include <optional>
#include "include/cppgc/platform.h"
#include "src/heap/cppgc/heap.h"
#include "src/heap/cppgc/stats-collector.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc::internal {
namespace {
class FakeGarbageCollector : public GarbageCollector {
public:
explicit FakeGarbageCollector(StatsCollector* stats_collector)
: stats_collector_(stats_collector) {}
void SetLiveBytes(size_t live_bytes) { live_bytes_ = live_bytes; }
void CollectGarbage(GCConfig config) override {
stats_collector_->NotifyMarkingStarted(CollectionType::kMajor,
GCConfig::MarkingType::kAtomic,
GCConfig::IsForcedGC::kNotForced);
stats_collector_->NotifyMarkingCompleted(live_bytes_);
stats_collector_->NotifySweepingCompleted(GCConfig::SweepingType::kAtomic);
callcount_++;
}
void StartIncrementalGarbageCollection(GCConfig config) override {
UNREACHABLE();
}
size_t epoch() const override { return callcount_; }
std::optional<EmbedderStackState> overridden_stack_state() const override {
return {};
}
void set_override_stack_state(EmbedderStackState state) override {}
void clear_overridden_stack_state() override {}
#ifdef V8_ENABLE_ALLOCATION_TIMEOUT
std::optional<int> UpdateAllocationTimeout() override { return std::nullopt; }
#endif // V8_ENABLE_ALLOCATION_TIMEOUT
private:
StatsCollector* stats_collector_;
size_t live_bytes_ = 0;
size_t callcount_ = 0;
};
class MockGarbageCollector : public GarbageCollector {
public:
MOCK_METHOD(void, CollectGarbage, (GCConfig), (override));
MOCK_METHOD(void, StartIncrementalGarbageCollection, (GCConfig), (override));
MOCK_METHOD(size_t, epoch, (), (const, override));
MOCK_METHOD(std::optional<EmbedderStackState>, overridden_stack_state, (),
(const, override));
MOCK_METHOD(void, set_override_stack_state, (EmbedderStackState), (override));
MOCK_METHOD(void, clear_overridden_stack_state, (), (override));
#ifdef V8_ENABLE_ALLOCATION_TIMEOUT
MOCK_METHOD(std::optional<int>, UpdateAllocationTimeout, (), (override));
#endif // V8_ENABLE_ALLOCATION_TIMEOUT
};
void FakeAllocate(StatsCollector* stats_collector, size_t bytes) {
stats_collector->NotifyAllocation(bytes);
stats_collector->NotifySafePointForConservativeCollection();
}
static constexpr Platform* kNoPlatform = nullptr;
} // namespace
TEST(HeapGrowingTest, ConservativeGCInvoked) {
StatsCollector stats_collector(kNoPlatform);
MockGarbageCollector gc;
cppgc::Heap::ResourceConstraints constraints;
// Force GC at the first update.
constraints.initial_heap_size_bytes = 1;
HeapGrowing growing(&gc, &stats_collector, constraints,
cppgc::Heap::MarkingType::kIncrementalAndConcurrent,
cppgc::Heap::SweepingType::kIncrementalAndConcurrent);
EXPECT_CALL(
gc, CollectGarbage(::testing::Field(
&GCConfig::stack_state, StackState::kMayContainHeapPointers)));
FakeAllocate(&stats_collector, 100 * kMB);
}
TEST(HeapGrowingTest, InitialHeapSize) {
StatsCollector stats_collector(kNoPlatform);
MockGarbageCollector gc;
cppgc::Heap::ResourceConstraints constraints;
// Use larger size to avoid running into small heap optimizations.
constexpr size_t kObjectSize = 10 * HeapGrowing::kMinLimitIncrease;
constraints.initial_heap_size_bytes = kObjectSize;
HeapGrowing growing(&gc, &stats_collector, constraints,
cppgc::Heap::MarkingType::kIncrementalAndConcurrent,
cppgc::Heap::SweepingType::kIncrementalAndConcurrent);
FakeAllocate(&stats_collector, kObjectSize - 1);
EXPECT_CALL(
gc, CollectGarbage(::testing::Field(
&GCConfig::stack_state, StackState::kMayContainHeapPointers)));
FakeAllocate(&stats_collector, kObjectSize);
}
TEST(HeapGrowingTest, ConstantGrowingFactor) {
// Use larger size to avoid running into small heap optimizations.
constexpr size_t kObjectSize = 10 * HeapGrowing::kMinLimitIncrease;
StatsCollector stats_collector(kNoPlatform);
FakeGarbageCollector gc(&stats_collector);
cppgc::Heap::ResourceConstraints constraints;
// Force GC at the first update.
constraints.initial_heap_size_bytes = HeapGrowing::kMinLimitIncrease;
HeapGrowing growing(&gc, &stats_collector, constraints,
cppgc::Heap::MarkingType::kIncrementalAndConcurrent,
cppgc::Heap::SweepingType::kIncrementalAndConcurrent);
EXPECT_EQ(0u, gc.epoch());
gc.SetLiveBytes(kObjectSize);
FakeAllocate(&stats_collector, kObjectSize + 1);
EXPECT_EQ(1u, gc.epoch());
EXPECT_EQ(1.5 * kObjectSize, growing.limit_for_atomic_gc());
}
TEST(HeapGrowingTest, SmallHeapGrowing) {
// Larger constant to avoid running into special handling for smaller heaps.
constexpr size_t kLargeAllocation = 100 * kMB;
StatsCollector stats_collector(kNoPlatform);
FakeGarbageCollector gc(&stats_collector);
cppgc::Heap::ResourceConstraints constraints;
// Force GC at the first update.
constraints.initial_heap_size_bytes = 1;
HeapGrowing growing(&gc, &stats_collector, constraints,
cppgc::Heap::MarkingType::kIncrementalAndConcurrent,
cppgc::Heap::SweepingType::kIncrementalAndConcurrent);
EXPECT_EQ(0u, gc.epoch());
gc.SetLiveBytes(1);
FakeAllocate(&stats_collector, kLargeAllocation);
EXPECT_EQ(1u, gc.epoch());
EXPECT_EQ(1 + HeapGrowing::kMinLimitIncrease, growing.limit_for_atomic_gc());
}
TEST(HeapGrowingTest, IncrementalGCStarted) {
StatsCollector stats_collector(kNoPlatform);
MockGarbageCollector gc;
cppgc::Heap::ResourceConstraints constraints;
HeapGrowing growing(&gc, &stats_collector, constraints,
cppgc::Heap::MarkingType::kIncrementalAndConcurrent,
cppgc::Heap::SweepingType::kIncrementalAndConcurrent);
EXPECT_CALL(
gc, CollectGarbage(::testing::Field(&GCConfig::stack_state,
StackState::kMayContainHeapPointers)))
.Times(0);
EXPECT_CALL(gc, StartIncrementalGarbageCollection(::testing::_));
// Allocate 1 byte less the limit for atomic gc to trigger incremental gc.
FakeAllocate(&stats_collector, growing.limit_for_atomic_gc() - 1);
}
TEST(HeapGrowingTest, IncrementalGCFinalized) {
StatsCollector stats_collector(kNoPlatform);
MockGarbageCollector gc;
cppgc::Heap::ResourceConstraints constraints;
HeapGrowing growing(&gc, &stats_collector, constraints,
cppgc::Heap::MarkingType::kIncrementalAndConcurrent,
cppgc::Heap::SweepingType::kIncrementalAndConcurrent);
EXPECT_CALL(
gc, CollectGarbage(::testing::Field(&GCConfig::stack_state,
StackState::kMayContainHeapPointers)))
.Times(0);
EXPECT_CALL(gc, StartIncrementalGarbageCollection(::testing::_));
// Allocate 1 byte less the limit for atomic gc to trigger incremental gc.
size_t bytes_for_incremental_gc = growing.limit_for_atomic_gc() - 1;
FakeAllocate(&stats_collector, bytes_for_incremental_gc);
::testing::Mock::VerifyAndClearExpectations(&gc);
EXPECT_CALL(
gc, CollectGarbage(::testing::Field(
&GCConfig::stack_state, StackState::kMayContainHeapPointers)));
EXPECT_CALL(gc, StartIncrementalGarbageCollection(::testing::_)).Times(0);
// Allocate the rest needed to trigger atomic gc ().
FakeAllocate(&stats_collector, StatsCollector::kAllocationThresholdBytes);
}
} // namespace cppgc::internal

View File

@ -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 "src/heap/cppgc/heap-object-header.h"
#include <atomic>
#include <memory>
#include "include/cppgc/allocation.h"
#include "src/base/atomic-utils.h"
#include "src/base/macros.h"
#include "src/base/platform/platform.h"
#include "src/heap/cppgc/globals.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
TEST(HeapObjectHeaderTest, Constructor) {
constexpr GCInfoIndex kGCInfoIndex = 17;
constexpr size_t kSize = kAllocationGranularity;
HeapObjectHeader header(kSize, kGCInfoIndex);
EXPECT_EQ(kSize, header.AllocatedSize());
EXPECT_EQ(kGCInfoIndex, header.GetGCInfoIndex());
EXPECT_TRUE(header.IsInConstruction());
EXPECT_FALSE(header.IsMarked());
}
TEST(HeapObjectHeaderTest, Payload) {
constexpr GCInfoIndex kGCInfoIndex = 17;
constexpr size_t kSize = kAllocationGranularity;
HeapObjectHeader header(kSize, kGCInfoIndex);
EXPECT_EQ(reinterpret_cast<ConstAddress>(&header) + sizeof(HeapObjectHeader),
header.ObjectStart());
}
TEST(HeapObjectHeaderTest, PayloadEnd) {
constexpr GCInfoIndex kGCInfoIndex = 17;
constexpr size_t kSize = kAllocationGranularity;
HeapObjectHeader header(kSize, kGCInfoIndex);
EXPECT_EQ(reinterpret_cast<ConstAddress>(&header) + kSize,
header.ObjectEnd());
}
TEST(HeapObjectHeaderTest, GetGCInfoIndex) {
constexpr GCInfoIndex kGCInfoIndex = 17;
constexpr size_t kSize = kAllocationGranularity;
HeapObjectHeader header(kSize, kGCInfoIndex);
EXPECT_EQ(kGCInfoIndex, header.GetGCInfoIndex());
EXPECT_EQ(kGCInfoIndex, header.GetGCInfoIndex<AccessMode::kAtomic>());
}
TEST(HeapObjectHeaderTest, AllocatedSize) {
constexpr GCInfoIndex kGCInfoIndex = 17;
constexpr size_t kSize = kAllocationGranularity * 23;
HeapObjectHeader header(kSize, kGCInfoIndex);
EXPECT_EQ(kSize, header.AllocatedSize());
EXPECT_EQ(kSize, header.AllocatedSize<AccessMode::kAtomic>());
}
TEST(HeapObjectHeaderTest, IsLargeObject) {
constexpr GCInfoIndex kGCInfoIndex = 17;
constexpr size_t kSize = kAllocationGranularity * 23;
HeapObjectHeader header(kSize, kGCInfoIndex);
EXPECT_EQ(false, header.IsLargeObject());
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<AccessMode::kAtomic>());
}
TEST(HeapObjectHeaderTest, MarkObjectAsFullyConstructed) {
constexpr GCInfoIndex kGCInfoIndex = 17;
constexpr size_t kSize = kAllocationGranularity;
HeapObjectHeader header(kSize, kGCInfoIndex);
EXPECT_TRUE(header.IsInConstruction());
header.MarkAsFullyConstructed();
EXPECT_FALSE(header.IsInConstruction());
// Size shares the same bitfield and should be unaffected by
// MarkObjectAsFullyConstructed.
EXPECT_EQ(kSize, header.AllocatedSize());
}
TEST(HeapObjectHeaderTest, TryMark) {
constexpr GCInfoIndex kGCInfoIndex = 17;
constexpr size_t kSize = kAllocationGranularity * 7;
HeapObjectHeader header(kSize, kGCInfoIndex);
EXPECT_FALSE(header.IsMarked());
EXPECT_TRUE(header.TryMarkAtomic());
// GCInfoIndex shares the same bitfield and should be unaffected by
// TryMarkAtomic.
EXPECT_EQ(kGCInfoIndex, header.GetGCInfoIndex());
EXPECT_FALSE(header.TryMarkAtomic());
// GCInfoIndex shares the same bitfield and should be unaffected by
// TryMarkAtomic.
EXPECT_EQ(kGCInfoIndex, header.GetGCInfoIndex());
EXPECT_TRUE(header.IsMarked());
}
TEST(HeapObjectHeaderTest, Unmark) {
constexpr GCInfoIndex kGCInfoIndex = 17;
constexpr size_t kSize = kAllocationGranularity * 7;
HeapObjectHeader header(kSize, kGCInfoIndex);
EXPECT_FALSE(header.IsMarked());
EXPECT_TRUE(header.TryMarkAtomic());
EXPECT_EQ(kGCInfoIndex, header.GetGCInfoIndex());
EXPECT_TRUE(header.IsMarked());
header.Unmark();
// GCInfoIndex shares the same bitfield and should be unaffected by Unmark.
EXPECT_EQ(kGCInfoIndex, header.GetGCInfoIndex());
EXPECT_FALSE(header.IsMarked());
HeapObjectHeader header2(kSize, kGCInfoIndex);
EXPECT_FALSE(header2.IsMarked());
EXPECT_TRUE(header2.TryMarkAtomic());
EXPECT_TRUE(header2.IsMarked());
header2.Unmark<AccessMode::kAtomic>();
// GCInfoIndex shares the same bitfield and should be unaffected by Unmark.
EXPECT_EQ(kGCInfoIndex, header2.GetGCInfoIndex());
EXPECT_FALSE(header2.IsMarked());
}
namespace {
struct Payload {
volatile size_t value{5};
};
class ConcurrentGCThread final : public v8::base::Thread {
public:
explicit ConcurrentGCThread(HeapObjectHeader* header, Payload* payload)
: v8::base::Thread(Options("Thread accessing object.")),
header_(header),
payload_(payload) {}
void Run() final {
while (header_->IsInConstruction<AccessMode::kAtomic>()) {
}
USE(v8::base::AsAtomicPtr(const_cast<size_t*>(&payload_->value))
->load(std::memory_order_relaxed));
}
private:
HeapObjectHeader* header_;
Payload* payload_;
};
} // namespace
TEST(HeapObjectHeaderTest, ConstructionBitProtectsNonAtomicWrites) {
// Object publishing: Test checks that non-atomic stores in the payload can be
// guarded using MarkObjectAsFullyConstructed/IsInConstruction. The test
// relies on TSAN to find data races.
constexpr size_t kSize =
(sizeof(HeapObjectHeader) + sizeof(Payload) + kAllocationMask) &
~kAllocationMask;
alignas(kAllocationGranularity) char data[kSize];
HeapObjectHeader* header = new (data) HeapObjectHeader(kSize, 1);
ConcurrentGCThread gc_thread(
header, reinterpret_cast<Payload*>(header->ObjectStart()));
CHECK(gc_thread.Start());
new (header->ObjectStart()) Payload();
header->MarkAsFullyConstructed();
gc_thread.Join();
}
#ifdef DEBUG
TEST(HeapObjectHeaderDeathTest, ConstructorTooLargeSize) {
constexpr GCInfoIndex kGCInfoIndex = 17;
constexpr size_t kSize = HeapObjectHeader::kMaxSize + 1;
EXPECT_DEATH_IF_SUPPORTED(HeapObjectHeader header(kSize, kGCInfoIndex), "");
}
TEST(HeapObjectHeaderDeathTest, ConstructorTooLargeGCInfoIndex) {
constexpr GCInfoIndex kGCInfoIndex = GCInfoTable::kMaxIndex + 1;
constexpr size_t kSize = kAllocationGranularity;
EXPECT_DEATH_IF_SUPPORTED(HeapObjectHeader header(kSize, kGCInfoIndex), "");
}
#endif // DEBUG
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,283 @@
// 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-page.h"
#include <algorithm>
#include "include/cppgc/allocation.h"
#include "include/cppgc/persistent.h"
#include "src/base/macros.h"
#include "src/heap/cppgc/globals.h"
#include "src/heap/cppgc/heap-object-header.h"
#include "src/heap/cppgc/page-memory.h"
#include "src/heap/cppgc/raw-heap.h"
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace {
class PageTest : public testing::TestWithHeap {
public:
RawHeap& GetRawHeap() { return Heap::From(GetHeap())->raw_heap(); }
PageBackend& GetPageBackend() {
return *Heap::From(GetHeap())->page_backend();
}
};
template <size_t Size>
class GCed : public GarbageCollected<GCed<Size>> {
public:
virtual void Trace(cppgc::Visitor*) const {}
char array[Size];
};
} // namespace
TEST_F(PageTest, SpaceIndexing) {
RawHeap& heap = GetRawHeap();
size_t space = 0u;
for (const auto& ptr : heap) {
EXPECT_EQ(&heap, ptr.get()->raw_heap());
EXPECT_EQ(space, ptr->index());
++space;
}
EXPECT_GE(space, RawHeap::kNumberOfRegularSpaces);
}
TEST_F(PageTest, PredefinedSpaces) {
using SpaceType = RawHeap::RegularSpaceType;
RawHeap& heap = GetRawHeap();
{
auto* gced = MakeGarbageCollected<GCed<1>>(GetAllocationHandle());
BaseSpace& space = NormalPage::FromPayload(gced)->space();
EXPECT_EQ(heap.Space(SpaceType::kNormal1), &space);
EXPECT_EQ(0u, space.index());
EXPECT_FALSE(space.is_large());
}
{
auto* gced = MakeGarbageCollected<GCed<32>>(GetAllocationHandle());
BaseSpace& space = NormalPage::FromPayload(gced)->space();
EXPECT_EQ(heap.Space(SpaceType::kNormal2), &space);
EXPECT_EQ(1u, space.index());
EXPECT_FALSE(space.is_large());
}
{
auto* gced = MakeGarbageCollected<GCed<64>>(GetAllocationHandle());
BaseSpace& space = NormalPage::FromPayload(gced)->space();
EXPECT_EQ(heap.Space(SpaceType::kNormal3), &space);
EXPECT_EQ(2u, space.index());
EXPECT_FALSE(space.is_large());
}
{
auto* gced = MakeGarbageCollected<GCed<128>>(GetAllocationHandle());
BaseSpace& space = NormalPage::FromPayload(gced)->space();
EXPECT_EQ(heap.Space(SpaceType::kNormal4), &space);
EXPECT_EQ(3u, space.index());
EXPECT_FALSE(space.is_large());
}
{
auto* gced = MakeGarbageCollected<GCed<2 * kLargeObjectSizeThreshold>>(
GetAllocationHandle());
BaseSpace& space = NormalPage::FromPayload(gced)->space();
EXPECT_EQ(heap.Space(SpaceType::kLarge), &space);
EXPECT_EQ(4u, space.index());
EXPECT_TRUE(space.is_large());
}
}
TEST_F(PageTest, NormalPageIndexing) {
using SpaceType = RawHeap::RegularSpaceType;
constexpr size_t kExpectedNumberOfPages = 10u;
constexpr size_t kObjectSize = 8u;
using Type = GCed<kObjectSize>;
static const size_t kNumberOfObjects =
(kExpectedNumberOfPages * NormalPage::PayloadSize() /
(sizeof(Type) + sizeof(HeapObjectHeader))) -
kExpectedNumberOfPages;
std::vector<Persistent<Type>> persistents(kNumberOfObjects);
for (auto& p : persistents) {
p = MakeGarbageCollected<Type>(GetAllocationHandle());
}
const RawHeap& heap = GetRawHeap();
const BaseSpace* space = heap.Space(SpaceType::kNormal1);
EXPECT_EQ(kExpectedNumberOfPages, space->size());
size_t page_n = 0;
for (const BasePage* page : *space) {
EXPECT_FALSE(page->is_large());
EXPECT_EQ(space, &page->space());
++page_n;
}
EXPECT_EQ(page_n, space->size());
}
TEST_F(PageTest, LargePageIndexing) {
using SpaceType = RawHeap::RegularSpaceType;
constexpr size_t kExpectedNumberOfPages = 10u;
constexpr size_t kObjectSize = 2 * kLargeObjectSizeThreshold;
using Type = GCed<kObjectSize>;
const size_t kNumberOfObjects = kExpectedNumberOfPages;
std::vector<Persistent<Type>> persistents(kNumberOfObjects);
for (auto& p : persistents) {
p = MakeGarbageCollected<Type>(GetAllocationHandle());
}
const RawHeap& heap = GetRawHeap();
const BaseSpace* space = heap.Space(SpaceType::kLarge);
EXPECT_EQ(kExpectedNumberOfPages, space->size());
size_t page_n = 0;
for (const BasePage* page : *space) {
EXPECT_TRUE(page->is_large());
++page_n;
}
EXPECT_EQ(page_n, space->size());
}
TEST_F(PageTest, HeapObjectHeaderOnBasePageIndexing) {
constexpr size_t kObjectSize = 8;
using Type = GCed<kObjectSize>;
const size_t kNumberOfObjects =
NormalPage::PayloadSize() / (sizeof(Type) + sizeof(HeapObjectHeader));
const size_t kLeftSpace =
NormalPage::PayloadSize() % (sizeof(Type) + sizeof(HeapObjectHeader));
std::vector<Persistent<Type>> persistents(kNumberOfObjects);
for (auto& p : persistents) {
p = MakeGarbageCollected<Type>(GetAllocationHandle());
}
const auto* page =
static_cast<NormalPage*>(BasePage::FromPayload(persistents[0].Get()));
size_t size = 0;
size_t num = 0;
for (const HeapObjectHeader& header : *page) {
EXPECT_EQ(reinterpret_cast<Address>(persistents[num].Get()),
header.ObjectStart());
size += header.AllocatedSize();
++num;
}
EXPECT_EQ(num, persistents.size());
EXPECT_EQ(size + kLeftSpace, NormalPage::PayloadSize());
}
TEST_F(PageTest, HeapObjectHeaderOnLargePageIndexing) {
constexpr size_t kObjectSize = 2 * kLargeObjectSizeThreshold;
using Type = GCed<kObjectSize>;
auto* gced = MakeGarbageCollected<Type>(GetAllocationHandle());
const auto* page = static_cast<LargePage*>(BasePage::FromPayload(gced));
const size_t expected_payload_size =
RoundUp(sizeof(Type) + sizeof(HeapObjectHeader), kAllocationGranularity);
EXPECT_EQ(expected_payload_size, page->PayloadSize());
const HeapObjectHeader* header = page->ObjectHeader();
EXPECT_EQ(reinterpret_cast<Address>(gced), header->ObjectStart());
}
TEST_F(PageTest, NormalPageCreationDestruction) {
RawHeap& heap = GetRawHeap();
const PageBackend* backend = Heap::From(GetHeap())->page_backend();
auto* space = static_cast<NormalPageSpace*>(
heap.Space(RawHeap::RegularSpaceType::kNormal1));
auto* page = NormalPage::TryCreate(GetPageBackend(), *space);
EXPECT_NE(nullptr, page);
EXPECT_NE(nullptr, backend->Lookup(page->PayloadStart()));
space->AddPage(page);
EXPECT_NE(space->end(), std::find(space->begin(), space->end(), page));
space->free_list().Add({page->PayloadStart(), page->PayloadSize()});
EXPECT_TRUE(space->free_list().ContainsForTesting(
{page->PayloadStart(), page->PayloadSize()}));
space->free_list().Clear();
EXPECT_FALSE(space->free_list().ContainsForTesting(
{page->PayloadStart(), page->PayloadSize()}));
space->RemovePage(page);
EXPECT_EQ(space->end(), std::find(space->begin(), space->end(), page));
NormalPage::Destroy(page);
EXPECT_EQ(nullptr, backend->Lookup(page->PayloadStart()));
}
TEST_F(PageTest, LargePageCreationDestruction) {
constexpr size_t kObjectSize = 2 * kLargeObjectSizeThreshold;
RawHeap& heap = GetRawHeap();
const PageBackend* backend = Heap::From(GetHeap())->page_backend();
auto* space = static_cast<LargePageSpace*>(
heap.Space(RawHeap::RegularSpaceType::kLarge));
auto* page = LargePage::TryCreate(GetPageBackend(), *space, kObjectSize);
EXPECT_NE(nullptr, page);
EXPECT_NE(nullptr, backend->Lookup(page->PayloadStart()));
space->AddPage(page);
EXPECT_NE(space->end(), std::find(space->begin(), space->end(), page));
space->RemovePage(page);
EXPECT_EQ(space->end(), std::find(space->begin(), space->end(), page));
LargePage::Destroy(page);
EXPECT_EQ(nullptr, backend->Lookup(page->PayloadStart()));
}
#if DEBUG
TEST_F(PageTest, UnsweptPageDestruction) {
RawHeap& heap = GetRawHeap();
{
auto* space = static_cast<NormalPageSpace*>(
heap.Space(RawHeap::RegularSpaceType::kNormal1));
auto* page = NormalPage::TryCreate(GetPageBackend(), *space);
EXPECT_NE(nullptr, page);
space->AddPage(page);
EXPECT_DEATH_IF_SUPPORTED(NormalPage::Destroy(page), "");
}
{
auto* space = static_cast<LargePageSpace*>(
heap.Space(RawHeap::RegularSpaceType::kLarge));
auto* page = LargePage::TryCreate(GetPageBackend(), *space,
2 * kLargeObjectSizeThreshold);
EXPECT_NE(nullptr, page);
space->AddPage(page);
EXPECT_DEATH_IF_SUPPORTED(LargePage::Destroy(page), "");
// Detach page and really destroy page in the parent process so that sweeper
// doesn't consider it.
space->RemovePage(page);
LargePage::Destroy(page);
}
}
#endif
TEST_F(PageTest, ObjectHeaderFromInnerAddress) {
{
auto* object = MakeGarbageCollected<GCed<64>>(GetAllocationHandle());
const HeapObjectHeader& expected = HeapObjectHeader::FromObject(object);
for (auto* inner_ptr = reinterpret_cast<ConstAddress>(object);
inner_ptr < reinterpret_cast<ConstAddress>(object + 1); ++inner_ptr) {
const HeapObjectHeader& hoh =
BasePage::FromPayload(object)->ObjectHeaderFromInnerAddress(
inner_ptr);
EXPECT_EQ(&expected, &hoh);
}
}
{
auto* object = MakeGarbageCollected<GCed<2 * kLargeObjectSizeThreshold>>(
GetAllocationHandle());
const HeapObjectHeader& expected = HeapObjectHeader::FromObject(object);
const HeapObjectHeader& hoh =
BasePage::FromPayload(object)->ObjectHeaderFromInnerAddress(
reinterpret_cast<ConstAddress>(object) + kLargeObjectSizeThreshold);
EXPECT_EQ(&expected, &hoh);
}
}
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,88 @@
// 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 <algorithm>
#include "include/cppgc/allocation.h"
#include "include/cppgc/heap.h"
#include "src/heap/cppgc/heap-base.h"
#include "src/heap/cppgc/process-heap.h"
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
class HeapRegistryTest : public testing::TestWithPlatform {};
TEST_F(HeapRegistryTest, Empty) {
EXPECT_EQ(0u, HeapRegistry::GetRegisteredHeapsForTesting().size());
}
namespace {
bool Contains(const HeapRegistry::Storage& storage, const cppgc::Heap* needle) {
return storage.end() !=
std::find(storage.begin(), storage.end(),
&cppgc::internal::Heap::From(needle)->AsBase());
}
} // namespace
TEST_F(HeapRegistryTest, RegisterUnregisterHeaps) {
const auto& storage = HeapRegistry::GetRegisteredHeapsForTesting();
EXPECT_EQ(0u, storage.size());
{
const auto heap1 = Heap::Create(platform_);
EXPECT_TRUE(Contains(storage, heap1.get()));
EXPECT_EQ(1u, storage.size());
{
const auto heap2 = Heap::Create(platform_);
EXPECT_TRUE(Contains(storage, heap1.get()));
EXPECT_TRUE(Contains(storage, heap2.get()));
EXPECT_EQ(2u, storage.size());
}
EXPECT_TRUE(Contains(storage, heap1.get()));
EXPECT_EQ(1u, storage.size());
}
EXPECT_EQ(0u, storage.size());
}
TEST_F(HeapRegistryTest, DoesNotFindNullptr) {
const auto heap = Heap::Create(platform_);
EXPECT_EQ(nullptr, HeapRegistry::TryFromManagedPointer(nullptr));
}
TEST_F(HeapRegistryTest, DoesNotFindStackAddress) {
const auto heap = Heap::Create(platform_);
EXPECT_EQ(nullptr, HeapRegistry::TryFromManagedPointer(&heap));
}
TEST_F(HeapRegistryTest, DoesNotFindOffHeap) {
const auto heap = Heap::Create(platform_);
auto dummy = std::make_unique<char>();
EXPECT_EQ(nullptr, HeapRegistry::TryFromManagedPointer(dummy.get()));
}
namespace {
class GCed final : public GarbageCollected<GCed> {
public:
void Trace(Visitor*) const {}
};
} // namespace
TEST_F(HeapRegistryTest, FindsRightHeapForOnHeapAddress) {
const auto heap1 = Heap::Create(platform_);
const auto heap2 = Heap::Create(platform_);
auto* o = MakeGarbageCollected<GCed>(heap1->GetAllocationHandle());
EXPECT_EQ(&cppgc::internal::Heap::From(heap1.get())->AsBase(),
HeapRegistry::TryFromManagedPointer(o));
EXPECT_NE(&cppgc::internal::Heap::From(heap2.get())->AsBase(),
HeapRegistry::TryFromManagedPointer(o));
}
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,227 @@
// Copyright 2021 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-statistics-collector.h"
#include "include/cppgc/allocation.h"
#include "include/cppgc/heap-statistics.h"
#include "include/cppgc/persistent.h"
#include "src/base/logging.h"
#include "src/base/macros.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 {
class HeapStatisticsCollectorTest : public testing::TestWithHeap {};
TEST_F(HeapStatisticsCollectorTest, EmptyHeapBriefStatisitcs) {
HeapStatistics brief_stats = Heap::From(GetHeap())->CollectStatistics(
HeapStatistics::DetailLevel::kBrief);
EXPECT_EQ(HeapStatistics::DetailLevel::kBrief, brief_stats.detail_level);
EXPECT_EQ(0u, brief_stats.used_size_bytes);
EXPECT_EQ(0u, brief_stats.used_size_bytes);
EXPECT_EQ(0u, brief_stats.pooled_memory_size_bytes);
EXPECT_TRUE(brief_stats.space_stats.empty());
}
TEST_F(HeapStatisticsCollectorTest, EmptyHeapDetailedStatisitcs) {
HeapStatistics detailed_stats = Heap::From(GetHeap())->CollectStatistics(
HeapStatistics::DetailLevel::kDetailed);
EXPECT_EQ(HeapStatistics::DetailLevel::kDetailed,
detailed_stats.detail_level);
EXPECT_EQ(0u, detailed_stats.used_size_bytes);
EXPECT_EQ(0u, detailed_stats.used_size_bytes);
EXPECT_EQ(0u, detailed_stats.pooled_memory_size_bytes);
EXPECT_EQ(RawHeap::kNumberOfRegularSpaces, detailed_stats.space_stats.size());
for (HeapStatistics::SpaceStatistics& space_stats :
detailed_stats.space_stats) {
EXPECT_EQ(0u, space_stats.used_size_bytes);
EXPECT_EQ(0u, space_stats.used_size_bytes);
EXPECT_TRUE(space_stats.page_stats.empty());
if (space_stats.name == "LargePageSpace") {
// Large page space has no free list.
EXPECT_TRUE(space_stats.free_list_stats.bucket_size.empty());
EXPECT_TRUE(space_stats.free_list_stats.free_count.empty());
EXPECT_TRUE(space_stats.free_list_stats.free_size.empty());
} else {
EXPECT_EQ(kPageSizeLog2, space_stats.free_list_stats.bucket_size.size());
EXPECT_EQ(kPageSizeLog2, space_stats.free_list_stats.free_count.size());
EXPECT_EQ(kPageSizeLog2, space_stats.free_list_stats.free_size.size());
}
}
}
namespace {
template <size_t Size>
class GCed : public GarbageCollected<GCed<Size>> {
public:
void Trace(Visitor*) const {}
private:
char array_[Size];
};
} // namespace
TEST_F(HeapStatisticsCollectorTest, NonEmptyNormalPage) {
MakeGarbageCollected<GCed<1>>(GetHeap()->GetAllocationHandle());
static constexpr size_t used_size =
RoundUp<kAllocationGranularity>(1 + sizeof(HeapObjectHeader));
HeapStatistics detailed_stats = Heap::From(GetHeap())->CollectStatistics(
HeapStatistics::DetailLevel::kDetailed);
EXPECT_EQ(HeapStatistics::DetailLevel::kDetailed,
detailed_stats.detail_level);
EXPECT_EQ(kPageSize, detailed_stats.committed_size_bytes);
EXPECT_EQ(kPageSize, detailed_stats.resident_size_bytes);
EXPECT_EQ(used_size, detailed_stats.used_size_bytes);
EXPECT_EQ(0u, detailed_stats.pooled_memory_size_bytes);
EXPECT_EQ(RawHeap::kNumberOfRegularSpaces, detailed_stats.space_stats.size());
bool found_non_empty_space = false;
for (const HeapStatistics::SpaceStatistics& space_stats :
detailed_stats.space_stats) {
if (space_stats.page_stats.empty()) {
EXPECT_EQ(0u, space_stats.committed_size_bytes);
EXPECT_EQ(0u, space_stats.resident_size_bytes);
EXPECT_EQ(0u, space_stats.used_size_bytes);
continue;
}
EXPECT_NE("LargePageSpace", space_stats.name);
EXPECT_FALSE(found_non_empty_space);
found_non_empty_space = true;
EXPECT_EQ(kPageSize, space_stats.committed_size_bytes);
EXPECT_EQ(kPageSize, space_stats.resident_size_bytes);
EXPECT_EQ(used_size, space_stats.used_size_bytes);
EXPECT_EQ(1u, space_stats.page_stats.size());
EXPECT_EQ(kPageSize, space_stats.page_stats.back().committed_size_bytes);
EXPECT_EQ(kPageSize, space_stats.page_stats.back().resident_size_bytes);
EXPECT_EQ(used_size, space_stats.page_stats.back().used_size_bytes);
}
EXPECT_TRUE(found_non_empty_space);
}
TEST_F(HeapStatisticsCollectorTest, NonEmptyLargePage) {
MakeGarbageCollected<GCed<kLargeObjectSizeThreshold>>(
GetHeap()->GetAllocationHandle());
static constexpr size_t used_size = RoundUp<kAllocationGranularity>(
kLargeObjectSizeThreshold + sizeof(HeapObjectHeader));
static constexpr size_t committed_size =
RoundUp<kAllocationGranularity>(used_size + LargePage::PageHeaderSize());
HeapStatistics detailed_stats = Heap::From(GetHeap())->CollectStatistics(
HeapStatistics::DetailLevel::kDetailed);
EXPECT_EQ(HeapStatistics::DetailLevel::kDetailed,
detailed_stats.detail_level);
EXPECT_EQ(committed_size, detailed_stats.committed_size_bytes);
EXPECT_EQ(committed_size, detailed_stats.resident_size_bytes);
EXPECT_EQ(used_size, detailed_stats.used_size_bytes);
EXPECT_EQ(0u, detailed_stats.pooled_memory_size_bytes);
EXPECT_EQ(RawHeap::kNumberOfRegularSpaces, detailed_stats.space_stats.size());
bool found_non_empty_space = false;
for (const HeapStatistics::SpaceStatistics& space_stats :
detailed_stats.space_stats) {
if (space_stats.page_stats.empty()) {
EXPECT_EQ(0u, space_stats.committed_size_bytes);
EXPECT_EQ(0u, space_stats.used_size_bytes);
continue;
}
EXPECT_EQ("LargePageSpace", space_stats.name);
EXPECT_FALSE(found_non_empty_space);
found_non_empty_space = true;
EXPECT_EQ(committed_size, space_stats.committed_size_bytes);
EXPECT_EQ(committed_size, space_stats.resident_size_bytes);
EXPECT_EQ(used_size, space_stats.used_size_bytes);
EXPECT_EQ(1u, space_stats.page_stats.size());
EXPECT_EQ(committed_size,
space_stats.page_stats.back().committed_size_bytes);
EXPECT_EQ(committed_size,
space_stats.page_stats.back().resident_size_bytes);
EXPECT_EQ(used_size, space_stats.page_stats.back().used_size_bytes);
}
EXPECT_TRUE(found_non_empty_space);
}
TEST_F(HeapStatisticsCollectorTest, BriefStatisticsWithDiscardingOnNormalPage) {
if (!Sweeper::CanDiscardMemory()) return;
Persistent<GCed<1>> holder =
MakeGarbageCollected<GCed<1>>(GetHeap()->GetAllocationHandle());
ConservativeMemoryDiscardingGC();
HeapStatistics brief_stats = Heap::From(GetHeap())->CollectStatistics(
HeapStatistics::DetailLevel::kBrief);
// Do not enforce exact resident_size_bytes here as this is an implementation
// detail of the sweeper.
EXPECT_GT(brief_stats.committed_size_bytes, brief_stats.resident_size_bytes);
EXPECT_EQ(0u, brief_stats.pooled_memory_size_bytes);
}
TEST_F(HeapStatisticsCollectorTest,
BriefStatisticsWithoutDiscardingOnNormalPage) {
if (!Sweeper::CanDiscardMemory()) return;
MakeGarbageCollected<GCed<1>>(GetHeap()->GetAllocationHandle());
// kNoHeapPointers: make the test deterministic, not depend on what the
// compiler does with the stack.
internal::Heap::From(GetHeap())->CollectGarbage(
{CollectionType::kMinor, Heap::StackState::kNoHeapPointers,
cppgc::Heap::MarkingType::kAtomic, cppgc::Heap::SweepingType::kAtomic,
GCConfig::FreeMemoryHandling::kDoNotDiscard});
HeapStatistics brief_stats = Heap::From(GetHeap())->CollectStatistics(
HeapStatistics::DetailLevel::kBrief);
// Pooled memory, since it wasn't discarded by the sweeper.
EXPECT_NE(brief_stats.pooled_memory_size_bytes, 0u);
// Pooled memory is committed and resident.
EXPECT_EQ(brief_stats.pooled_memory_size_bytes,
brief_stats.resident_size_bytes);
EXPECT_EQ(brief_stats.pooled_memory_size_bytes,
brief_stats.committed_size_bytes);
// But not allocated.
EXPECT_EQ(brief_stats.used_size_bytes, 0u);
// Pooled memory goes away when discarding, and is not accounted for once
// discarded.
internal::Heap::From(GetHeap())->CollectGarbage(
{CollectionType::kMinor, Heap::StackState::kMayContainHeapPointers,
cppgc::Heap::MarkingType::kAtomic, cppgc::Heap::SweepingType::kAtomic,
GCConfig::FreeMemoryHandling::kDiscardWherePossible});
brief_stats = Heap::From(GetHeap())->CollectStatistics(
HeapStatistics::DetailLevel::kBrief);
EXPECT_EQ(0u, brief_stats.pooled_memory_size_bytes);
EXPECT_EQ(0u, brief_stats.resident_size_bytes);
EXPECT_EQ(0u, brief_stats.committed_size_bytes);
EXPECT_EQ(0u, brief_stats.used_size_bytes);
}
TEST_F(HeapStatisticsCollectorTest,
DetailedStatisticsWithDiscardingOnNormalPage) {
if (!Sweeper::CanDiscardMemory()) return;
Persistent<GCed<1>> holder =
MakeGarbageCollected<GCed<1>>(GetHeap()->GetAllocationHandle());
ConservativeMemoryDiscardingGC();
HeapStatistics detailed_stats = Heap::From(GetHeap())->CollectStatistics(
HeapStatistics::DetailLevel::kDetailed);
// Do not enforce exact resident_size_bytes here as this is an implementation
// detail of the sweeper.
EXPECT_GT(detailed_stats.committed_size_bytes,
detailed_stats.resident_size_bytes);
EXPECT_EQ(0u, detailed_stats.pooled_memory_size_bytes);
bool found_page = false;
for (const auto& space_stats : detailed_stats.space_stats) {
if (space_stats.committed_size_bytes == 0) continue;
// We should find a single page here that contains memory that was
// discarded.
EXPECT_EQ(1u, space_stats.page_stats.size());
const auto& page_stats = space_stats.page_stats[0];
EXPECT_GT(page_stats.committed_size_bytes, page_stats.resident_size_bytes);
found_page = true;
}
EXPECT_TRUE(found_page);
}
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,433 @@
// 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 <algorithm>
#include <iterator>
#include <numeric>
#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<Foo> {
public:
static size_t destructor_callcount;
Foo() { destructor_callcount = 0; }
~Foo() { destructor_callcount++; }
void Trace(cppgc::Visitor*) const {}
};
size_t Foo::destructor_callcount;
template <size_t Size>
class GCed : public GarbageCollected<GCed<Size>> {
public:
void Trace(cppgc::Visitor*) const {}
char buf[Size];
};
} // namespace
TEST_F(GCHeapTest, PreciseGCReclaimsObjectOnStack) {
Foo* volatile do_not_access =
MakeGarbageCollected<Foo>(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<Foo>(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<GCedWithFinalizer> {
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<LargeObjectGCDuringCtor> {
public:
static constexpr size_t kDataSize = kLargeObjectSizeThreshold + 1;
explicit LargeObjectGCDuringCtor(cppgc::Heap* heap)
: child_(MakeGarbageCollected<GCedWithFinalizer>(
heap->GetAllocationHandle())) {
internal::Heap::From(heap)->CollectGarbage(
GCConfig::ConservativeAtomicConfig());
}
void Trace(Visitor* visitor) const { visitor->Trace(child_); }
char data[kDataSize];
Member<GCedWithFinalizer> child_;
};
} // namespace
TEST_F(GCHeapTest, ConservativeGCFromLargeObjectCtorFindsObject) {
GCedWithFinalizer::destructor_counter = 0;
MakeGarbageCollected<LargeObjectGCDuringCtor>(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<GCed<kObjectSizes[0]>>(GetAllocationHandle());
MakeGarbageCollected<GCed<kObjectSizes[1]>>(GetAllocationHandle());
MakeGarbageCollected<GCed<kObjectSizes[2]>>(GetAllocationHandle());
MakeGarbageCollected<GCed<kObjectSizes[3]>>(GetAllocationHandle());
MakeGarbageCollected<GCed<kObjectSizes[4]>>(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<Foo>(GetAllocationHandle());
EXPECT_LE(kBaseSize, HeapObjectHeader::FromObject(object).AllocatedSize());
}
{
Foo* object = MakeGarbageCollected<Foo>(GetAllocationHandle(),
AdditionalBytes(kAdditionalBytes));
EXPECT_LE(kBaseSize + kAdditionalBytes,
HeapObjectHeader::FromObject(object).AllocatedSize());
}
{
Foo* object = MakeGarbageCollected<Foo>(
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<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::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<GCedExpectSweepingOnOwningThread> {
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<GCedExpectSweepingOnOwningThread>(
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<ExpectAtomicPause> {
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<ExpectAtomicPause>(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> foo = MakeGarbageCollected<Foo>(GetAllocationHandle());
EXPECT_TRUE(foo.Get());
Heap::From(GetHeap())->Terminate();
EXPECT_FALSE(foo.Get());
}
TEST_F(GCHeapTest, TerminateInvokesDestructor) {
Persistent<Foo> foo = MakeGarbageCollected<Foo>(GetAllocationHandle());
EXPECT_EQ(0u, Foo::destructor_callcount);
Heap::From(GetHeap())->Terminate();
EXPECT_EQ(1u, Foo::destructor_callcount);
}
namespace {
template <template <typename> class PersistentType>
class Cloner final : public GarbageCollected<Cloner<PersistentType>> {
public:
static size_t destructor_count;
Cloner(cppgc::AllocationHandle& handle, size_t count)
: handle_(handle), count_(count) {}
~Cloner() {
EXPECT_FALSE(new_instance_);
destructor_count++;
if (count_) {
new_instance_ =
MakeGarbageCollected<Cloner>(handle_, handle_, count_ - 1);
}
}
void Trace(Visitor*) const {}
private:
static PersistentType<Cloner> new_instance_;
cppgc::AllocationHandle& handle_;
size_t count_;
};
// static
template <template <typename> class PersistentType>
PersistentType<Cloner<PersistentType>> Cloner<PersistentType>::new_instance_;
// static
template <template <typename> class PersistentType>
size_t Cloner<PersistentType>::destructor_count;
} // namespace
template <template <typename> class PersistentType>
void TerminateReclaimsNewState(std::shared_ptr<Platform> platform) {
auto heap = cppgc::Heap::Create(platform);
using ClonerImpl = Cloner<PersistentType>;
Persistent<ClonerImpl> cloner = MakeGarbageCollected<ClonerImpl>(
heap->GetAllocationHandle(), heap->GetAllocationHandle(), 1);
ClonerImpl::destructor_count = 0;
EXPECT_TRUE(cloner.Get());
Heap::From(heap.get())->Terminate();
EXPECT_FALSE(cloner.Get());
EXPECT_EQ(2u, ClonerImpl::destructor_count);
}
TEST_F(GCHeapTest, TerminateReclaimsNewState) {
TerminateReclaimsNewState<Persistent>(GetPlatformHandle());
TerminateReclaimsNewState<WeakPersistent>(GetPlatformHandle());
TerminateReclaimsNewState<cppgc::subtle::CrossThreadPersistent>(
GetPlatformHandle());
TerminateReclaimsNewState<cppgc::subtle::WeakCrossThreadPersistent>(
GetPlatformHandle());
}
TEST_F(GCHeapDeathTest, TerminateProhibitsAllocation) {
Heap::From(GetHeap())->Terminate();
EXPECT_DEATH_IF_SUPPORTED(MakeGarbageCollected<Foo>(GetAllocationHandle()),
"");
}
template <template <typename> class PersistentType>
void LargeChainOfNewStates(cppgc::Heap& heap) {
using ClonerImpl = Cloner<PersistentType>;
Persistent<ClonerImpl> cloner = MakeGarbageCollected<ClonerImpl>(
heap.GetAllocationHandle(), heap.GetAllocationHandle(), 1000);
ClonerImpl::destructor_count = 0;
EXPECT_TRUE(cloner.Get());
// Terminate() requires destructors to stop creating new state within a few
// garbage collections.
EXPECT_DEATH_IF_SUPPORTED(Heap::From(&heap)->Terminate(), "");
}
TEST_F(GCHeapDeathTest, LargeChainOfNewStatesPersistent) {
LargeChainOfNewStates<Persistent>(*GetHeap());
}
TEST_F(GCHeapDeathTest, LargeChainOfNewStatesCrossThreadPersistent) {
LargeChainOfNewStates<subtle::CrossThreadPersistent>(*GetHeap());
}
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,45 @@
// Copyright 2021 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/liveness-broker.h"
#include "include/cppgc/allocation.h"
#include "include/cppgc/garbage-collected.h"
#include "src/heap/cppgc/heap-object-header.h"
#include "src/heap/cppgc/liveness-broker.h"
#include "test/unittests/heap/cppgc/tests.h"
namespace cppgc {
namespace internal {
namespace {
using LivenessBrokerTest = testing::TestSupportingAllocationOnly;
class GCed : public GarbageCollected<GCed> {
public:
void Trace(cppgc::Visitor*) const {}
};
} // namespace
TEST_F(LivenessBrokerTest, IsHeapObjectAliveForConstPointer) {
// Regression test: http://crbug.com/661363.
GCed* object = MakeGarbageCollected<GCed>(GetAllocationHandle());
HeapObjectHeader& header = HeapObjectHeader::FromObject(object);
LivenessBroker broker = internal::LivenessBrokerFactory::Create();
EXPECT_TRUE(header.TryMarkAtomic());
EXPECT_TRUE(broker.IsHeapObjectAlive(object));
const GCed* const_object = const_cast<const GCed*>(object);
EXPECT_TRUE(broker.IsHeapObjectAlive(const_object));
}
TEST_F(LivenessBrokerTest, IsHeapObjectAliveNullptr) {
GCed* object = nullptr;
LivenessBroker broker = internal::LivenessBrokerFactory::Create();
EXPECT_TRUE(broker.IsHeapObjectAlive(object));
}
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,77 @@
// 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/logging.h"
#include <string>
#include "include/cppgc/source-location.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace {
// GCC < 9 has a bug due to which calling non-constexpr functions are not
// allowed even on constexpr path:
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67026.
#if !defined(__GNUC__) || defined(__clang__)
constexpr int CheckInConstexpr(int a) {
CPPGC_DCHECK(a > 0);
CPPGC_CHECK(a > 0);
return a;
}
#endif
} // namespace
TEST(LoggingTest, Pass) {
CPPGC_DCHECK(true);
CPPGC_CHECK(true);
}
TEST(LoggingTest, Fail) {
#if DEBUG
EXPECT_DEATH_IF_SUPPORTED(CPPGC_DCHECK(false), "");
#endif
EXPECT_DEATH_IF_SUPPORTED(CPPGC_CHECK(false), "");
}
TEST(LoggingTest, DontReportUnused) {
int a = 1;
CPPGC_DCHECK(a);
}
#if !defined(__GNUC__) || defined(__clang__)
TEST(LoggingTest, ConstexprContext) {
constexpr int a = CheckInConstexpr(1);
CPPGC_DCHECK(a);
}
#endif
#if DEBUG && !defined(OFFICIAL_BUILD) && GTEST_HAS_DEATH_TEST
TEST(LoggingTest, Message) {
using ::testing::ContainsRegex;
EXPECT_DEATH_IF_SUPPORTED(CPPGC_DCHECK(5 == 7),
ContainsRegex("failed.*5 == 7"));
EXPECT_DEATH_IF_SUPPORTED(CPPGC_CHECK(5 == 7),
ContainsRegex("failed.*5 == 7"));
}
#if V8_SUPPORTS_SOURCE_LOCATION
TEST(LoggingTest, SourceLocation) {
using ::testing::AllOf;
using ::testing::HasSubstr;
// clang-format off
constexpr auto loc = SourceLocation::Current();
EXPECT_DEATH_IF_SUPPORTED(CPPGC_DCHECK(false), AllOf(HasSubstr(loc.FileName()), HasSubstr(std::to_string(loc.Line() + 1)))); // NOLINT(whitespace/line_length)
EXPECT_DEATH_IF_SUPPORTED(CPPGC_CHECK(false), AllOf(HasSubstr(loc.FileName()), HasSubstr(std::to_string(loc.Line() + 2)))); // NOLINT(whitespace/line_length)
// clang-format on
}
#endif // V8_SUPPORTS_SOURCE_LOCATION
#endif // DEBUG
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,496 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/heap/cppgc/marker.h"
#include <memory>
#include "include/cppgc/allocation.h"
#include "include/cppgc/internal/pointer-policies.h"
#include "include/cppgc/member.h"
#include "include/cppgc/persistent.h"
#include "include/cppgc/trace-trait.h"
#include "include/cppgc/visitor.h"
#include "src/heap/cppgc/heap-object-header.h"
#include "src/heap/cppgc/marking-visitor.h"
#include "src/heap/cppgc/object-allocator.h"
#include "src/heap/cppgc/stats-collector.h"
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace {
class MarkerTest : public testing::TestWithHeap {
public:
void DoMarking(StackState stack_state) {
const MarkingConfig config = {CollectionType::kMajor, stack_state};
auto* heap = Heap::From(GetHeap());
InitializeMarker(*heap, GetPlatformHandle().get(), config);
marker_->FinishMarking(stack_state);
// Pretend do finish sweeping as StatsCollector verifies that Notify*
// methods are called in the right order.
heap->stats_collector()->NotifySweepingCompleted(
GCConfig::SweepingType::kAtomic);
}
void InitializeMarker(HeapBase& heap, cppgc::Platform* platform,
MarkingConfig config) {
marker_ = std::make_unique<Marker>(heap, platform, config);
marker_->StartMarking();
}
Marker* marker() const { return marker_.get(); }
void ResetMarker() { marker_.reset(); }
private:
std::unique_ptr<Marker> marker_;
};
class GCed : public GarbageCollected<GCed> {
public:
void SetChild(GCed* child) { child_ = child; }
void SetWeakChild(GCed* child) { weak_child_ = child; }
GCed* child() const { return child_.Get(); }
GCed* weak_child() const { return weak_child_.Get(); }
void Trace(cppgc::Visitor* visitor) const {
visitor->Trace(child_);
visitor->Trace(weak_child_);
}
private:
Member<GCed> child_;
WeakMember<GCed> weak_child_;
};
template <typename T>
V8_NOINLINE T access(volatile const T& t) {
return t;
}
} // namespace
TEST_F(MarkerTest, PersistentIsMarked) {
Persistent<GCed> object = MakeGarbageCollected<GCed>(GetAllocationHandle());
HeapObjectHeader& header = HeapObjectHeader::FromObject(object);
EXPECT_FALSE(header.IsMarked());
DoMarking(StackState::kNoHeapPointers);
EXPECT_TRUE(header.IsMarked());
}
TEST_F(MarkerTest, ReachableMemberIsMarked) {
Persistent<GCed> parent = MakeGarbageCollected<GCed>(GetAllocationHandle());
parent->SetChild(MakeGarbageCollected<GCed>(GetAllocationHandle()));
HeapObjectHeader& header = HeapObjectHeader::FromObject(parent->child());
EXPECT_FALSE(header.IsMarked());
DoMarking(StackState::kNoHeapPointers);
EXPECT_TRUE(header.IsMarked());
}
TEST_F(MarkerTest, UnreachableMemberIsNotMarked) {
Member<GCed> object = MakeGarbageCollected<GCed>(GetAllocationHandle());
HeapObjectHeader& header = HeapObjectHeader::FromObject(object);
EXPECT_FALSE(header.IsMarked());
DoMarking(StackState::kNoHeapPointers);
EXPECT_FALSE(header.IsMarked());
}
TEST_F(MarkerTest, ObjectReachableFromStackIsMarked) {
GCed* object = MakeGarbageCollected<GCed>(GetAllocationHandle());
EXPECT_FALSE(HeapObjectHeader::FromObject(object).IsMarked());
DoMarking(StackState::kMayContainHeapPointers);
EXPECT_TRUE(HeapObjectHeader::FromObject(object).IsMarked());
access(object);
}
TEST_F(MarkerTest, ObjectReachableOnlyFromStackIsNotMarkedIfStackIsEmpty) {
GCed* object = MakeGarbageCollected<GCed>(GetAllocationHandle());
HeapObjectHeader& header = HeapObjectHeader::FromObject(object);
EXPECT_FALSE(header.IsMarked());
DoMarking(StackState::kNoHeapPointers);
EXPECT_FALSE(header.IsMarked());
access(object);
}
TEST_F(MarkerTest, WeakReferenceToUnreachableObjectIsCleared) {
{
WeakPersistent<GCed> weak_object =
MakeGarbageCollected<GCed>(GetAllocationHandle());
EXPECT_TRUE(weak_object);
DoMarking(StackState::kNoHeapPointers);
EXPECT_FALSE(weak_object);
}
{
Persistent<GCed> parent = MakeGarbageCollected<GCed>(GetAllocationHandle());
parent->SetWeakChild(MakeGarbageCollected<GCed>(GetAllocationHandle()));
EXPECT_TRUE(parent->weak_child());
DoMarking(StackState::kNoHeapPointers);
EXPECT_FALSE(parent->weak_child());
}
}
TEST_F(MarkerTest, WeakReferenceToReachableObjectIsNotCleared) {
// Reachable from Persistent
{
Persistent<GCed> object = MakeGarbageCollected<GCed>(GetAllocationHandle());
WeakPersistent<GCed> weak_object(object);
EXPECT_TRUE(weak_object);
DoMarking(StackState::kNoHeapPointers);
EXPECT_TRUE(weak_object);
}
{
Persistent<GCed> object = MakeGarbageCollected<GCed>(GetAllocationHandle());
Persistent<GCed> parent = MakeGarbageCollected<GCed>(GetAllocationHandle());
parent->SetWeakChild(object);
EXPECT_TRUE(parent->weak_child());
DoMarking(StackState::kNoHeapPointers);
EXPECT_TRUE(parent->weak_child());
}
// Reachable from Member
{
Persistent<GCed> parent = MakeGarbageCollected<GCed>(GetAllocationHandle());
WeakPersistent<GCed> weak_object(
MakeGarbageCollected<GCed>(GetAllocationHandle()));
parent->SetChild(weak_object);
EXPECT_TRUE(weak_object);
DoMarking(StackState::kNoHeapPointers);
EXPECT_TRUE(weak_object);
}
{
Persistent<GCed> parent = MakeGarbageCollected<GCed>(GetAllocationHandle());
parent->SetChild(MakeGarbageCollected<GCed>(GetAllocationHandle()));
parent->SetWeakChild(parent->child());
EXPECT_TRUE(parent->weak_child());
DoMarking(StackState::kNoHeapPointers);
EXPECT_TRUE(parent->weak_child());
}
// Reachable from stack
{
GCed* object = MakeGarbageCollected<GCed>(GetAllocationHandle());
WeakPersistent<GCed> weak_object(object);
EXPECT_TRUE(weak_object);
DoMarking(StackState::kMayContainHeapPointers);
EXPECT_TRUE(weak_object);
access(object);
}
{
GCed* object = MakeGarbageCollected<GCed>(GetAllocationHandle());
Persistent<GCed> parent = MakeGarbageCollected<GCed>(GetAllocationHandle());
parent->SetWeakChild(object);
EXPECT_TRUE(parent->weak_child());
DoMarking(StackState::kMayContainHeapPointers);
EXPECT_TRUE(parent->weak_child());
access(object);
}
}
TEST_F(MarkerTest, DeepHierarchyIsMarked) {
static constexpr int kHierarchyDepth = 10;
Persistent<GCed> root = MakeGarbageCollected<GCed>(GetAllocationHandle());
GCed* parent = root;
for (int i = 0; i < kHierarchyDepth; ++i) {
parent->SetChild(MakeGarbageCollected<GCed>(GetAllocationHandle()));
parent->SetWeakChild(parent->child());
parent = parent->child();
}
DoMarking(StackState::kNoHeapPointers);
EXPECT_TRUE(HeapObjectHeader::FromObject(root).IsMarked());
parent = root;
for (int i = 0; i < kHierarchyDepth; ++i) {
EXPECT_TRUE(HeapObjectHeader::FromObject(parent->child()).IsMarked());
EXPECT_TRUE(parent->weak_child());
parent = parent->child();
}
}
TEST_F(MarkerTest, NestedObjectsOnStackAreMarked) {
GCed* root = MakeGarbageCollected<GCed>(GetAllocationHandle());
root->SetChild(MakeGarbageCollected<GCed>(GetAllocationHandle()));
root->child()->SetChild(MakeGarbageCollected<GCed>(GetAllocationHandle()));
DoMarking(StackState::kMayContainHeapPointers);
EXPECT_TRUE(HeapObjectHeader::FromObject(root).IsMarked());
EXPECT_TRUE(HeapObjectHeader::FromObject(root->child()).IsMarked());
EXPECT_TRUE(HeapObjectHeader::FromObject(root->child()->child()).IsMarked());
}
namespace {
class GCedWithCallback : public GarbageCollected<GCedWithCallback> {
public:
template <typename Callback>
explicit GCedWithCallback(Callback callback) {
callback(this);
}
template <typename Callback>
GCedWithCallback(Callback callback, GCed* gced) : gced_(gced) {
callback(this);
}
void Trace(Visitor* visitor) const { visitor->Trace(gced_); }
GCed* gced() const { return gced_; }
private:
Member<GCed> gced_;
};
} // namespace
TEST_F(MarkerTest, InConstructionObjectIsEventuallyMarkedEmptyStack) {
static const MarkingConfig config = {CollectionType::kMajor,
StackState::kMayContainHeapPointers};
InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get(), config);
GCedWithCallback* object = MakeGarbageCollected<GCedWithCallback>(
GetAllocationHandle(), [marker = marker()](GCedWithCallback* obj) {
Member<GCedWithCallback> member(obj);
marker->Visitor().Trace(member);
});
EXPECT_FALSE(HeapObjectHeader::FromObject(object).IsMarked());
marker()->FinishMarking(StackState::kMayContainHeapPointers);
EXPECT_TRUE(HeapObjectHeader::FromObject(object).IsMarked());
}
TEST_F(MarkerTest, InConstructionObjectIsEventuallyMarkedNonEmptyStack) {
static const MarkingConfig config = {CollectionType::kMajor,
StackState::kMayContainHeapPointers};
InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get(), config);
MakeGarbageCollected<GCedWithCallback>(
GetAllocationHandle(), [marker = marker()](GCedWithCallback* obj) {
Member<GCedWithCallback> member(obj);
marker->Visitor().Trace(member);
EXPECT_FALSE(HeapObjectHeader::FromObject(obj).IsMarked());
marker->FinishMarking(StackState::kMayContainHeapPointers);
EXPECT_TRUE(HeapObjectHeader::FromObject(obj).IsMarked());
});
}
namespace {
// Storage that can be used to hide a pointer from the GC. Only useful when
// dealing with the stack separately.
class GCObliviousObjectStorage final {
public:
GCObliviousObjectStorage()
: storage_(std::make_unique<const void*>(nullptr)) {}
template <typename T>
void set_object(T* t) {
*storage_.get() = TraceTrait<T>::GetTraceDescriptor(t).base_object_payload;
}
const void* object() const { return *storage_; }
private:
std::unique_ptr<const void*> storage_;
};
V8_NOINLINE void RegisterInConstructionObject(
AllocationHandle& allocation_handle, Visitor& v,
GCObliviousObjectStorage& storage) {
// Create deeper stack to avoid finding any temporary reference in the caller.
char space[500];
USE(space);
MakeGarbageCollected<GCedWithCallback>(
allocation_handle,
[&visitor = v, &storage](GCedWithCallback* obj) {
Member<GCedWithCallback> member(obj);
// Adds GCedWithCallback to in-construction objects.
visitor.Trace(member);
EXPECT_FALSE(HeapObjectHeader::FromObject(obj).IsMarked());
// The inner object GCed is only found if GCedWithCallback is processed.
storage.set_object(obj->gced());
},
// Initializing store does not trigger a write barrier.
MakeGarbageCollected<GCed>(allocation_handle));
}
} // namespace
TEST_F(MarkerTest,
InConstructionObjectIsEventuallyMarkedDifferentNonEmptyStack) {
static const MarkingConfig config = {CollectionType::kMajor,
StackState::kMayContainHeapPointers};
InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get(), config);
GCObliviousObjectStorage storage;
RegisterInConstructionObject(GetAllocationHandle(), marker()->Visitor(),
storage);
EXPECT_FALSE(HeapObjectHeader::FromObject(storage.object()).IsMarked());
marker()->FinishMarking(StackState::kMayContainHeapPointers);
EXPECT_TRUE(HeapObjectHeader::FromObject(storage.object()).IsMarked());
}
TEST_F(MarkerTest, SentinelNotClearedOnWeakPersistentHandling) {
static const MarkingConfig config = {
CollectionType::kMajor, StackState::kNoHeapPointers,
MarkingConfig::MarkingType::kIncremental};
Persistent<GCed> root = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto* tmp = MakeGarbageCollected<GCed>(GetAllocationHandle());
root->SetWeakChild(tmp);
InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get(), config);
while (!marker()->IncrementalMarkingStepForTesting(
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::FromObject(root.Get()).IsMarked());
root->SetWeakChild(kSentinelPointer);
marker()->FinishMarking(StackState::kNoHeapPointers);
EXPECT_EQ(kSentinelPointer, root->weak_child());
}
namespace {
class SimpleObject final : public GarbageCollected<SimpleObject> {
public:
void Trace(Visitor*) const {}
};
class ObjectWithEphemeronPair final
: public GarbageCollected<ObjectWithEphemeronPair> {
public:
explicit ObjectWithEphemeronPair(AllocationHandle& handle)
: ephemeron_pair_(MakeGarbageCollected<SimpleObject>(handle),
MakeGarbageCollected<SimpleObject>(handle)) {}
void Trace(Visitor* visitor) const {
// First trace the ephemeron pair. The key is not yet marked as live, so the
// pair should be recorded for later processing. Then strongly mark the key.
// Marking the key will not trigger another worklist processing iteration,
// as it merely continues the same loop for regular objects and will leave
// the main marking worklist empty. If recording the ephemeron pair doesn't
// as well, we will get a crash when destroying the marker.
visitor->Trace(ephemeron_pair_);
visitor->TraceStrongly(ephemeron_pair_.key);
}
private:
const EphemeronPair<SimpleObject, SimpleObject> ephemeron_pair_;
};
} // namespace
TEST_F(MarkerTest, MarkerProcessesAllEphemeronPairs) {
static const MarkingConfig config = {CollectionType::kMajor,
StackState::kNoHeapPointers,
MarkingConfig::MarkingType::kAtomic};
Persistent<ObjectWithEphemeronPair> obj =
MakeGarbageCollected<ObjectWithEphemeronPair>(GetAllocationHandle(),
GetAllocationHandle());
InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get(), config);
marker()->FinishMarking(StackState::kNoHeapPointers);
ResetMarker();
}
// Incremental Marking
class IncrementalMarkingTest : public testing::TestWithHeap {
public:
static constexpr MarkingConfig IncrementalPreciseMarkingConfig = {
CollectionType::kMajor, StackState::kNoHeapPointers,
MarkingConfig::MarkingType::kIncremental};
void FinishSteps(StackState stack_state) {
while (!SingleStep(stack_state)) {
}
}
void FinishMarking() {
GetMarkerRef()->FinishMarking(StackState::kMayContainHeapPointers);
// Pretend do finish sweeping as StatsCollector verifies that Notify*
// methods are called in the right order.
GetMarkerRef().reset();
Heap::From(GetHeap())->stats_collector()->NotifySweepingCompleted(
GCConfig::SweepingType::kIncremental);
}
void InitializeMarker(HeapBase& heap, cppgc::Platform* platform,
MarkingConfig config) {
GetMarkerRef() = std::make_unique<Marker>(heap, platform, config);
GetMarkerRef()->StartMarking();
}
MarkerBase* marker() const { return Heap::From(GetHeap())->marker(); }
private:
bool SingleStep(StackState stack_state) {
return GetMarkerRef()->IncrementalMarkingStepForTesting(stack_state);
}
};
constexpr MarkingConfig IncrementalMarkingTest::IncrementalPreciseMarkingConfig;
TEST_F(IncrementalMarkingTest, RootIsMarkedAfterMarkingStarted) {
Persistent<GCed> root = MakeGarbageCollected<GCed>(GetAllocationHandle());
EXPECT_FALSE(HeapObjectHeader::FromObject(root).IsMarked());
InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get(),
IncrementalPreciseMarkingConfig);
EXPECT_TRUE(HeapObjectHeader::FromObject(root).IsMarked());
FinishMarking();
}
TEST_F(IncrementalMarkingTest, MemberIsMarkedAfterMarkingSteps) {
Persistent<GCed> root = MakeGarbageCollected<GCed>(GetAllocationHandle());
root->SetChild(MakeGarbageCollected<GCed>(GetAllocationHandle()));
HeapObjectHeader& header = HeapObjectHeader::FromObject(root->child());
EXPECT_FALSE(header.IsMarked());
InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get(),
IncrementalPreciseMarkingConfig);
FinishSteps(StackState::kNoHeapPointers);
EXPECT_TRUE(header.IsMarked());
FinishMarking();
}
TEST_F(IncrementalMarkingTest,
MemberWithWriteBarrierIsMarkedAfterMarkingSteps) {
Persistent<GCed> root = MakeGarbageCollected<GCed>(GetAllocationHandle());
InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get(),
IncrementalPreciseMarkingConfig);
root->SetChild(MakeGarbageCollected<GCed>(GetAllocationHandle()));
FinishSteps(StackState::kNoHeapPointers);
HeapObjectHeader& header = HeapObjectHeader::FromObject(root->child());
EXPECT_TRUE(header.IsMarked());
FinishMarking();
}
namespace {
class Holder : public GarbageCollected<Holder> {
public:
void Trace(Visitor* visitor) const { visitor->Trace(member_); }
Member<GCedWithCallback> member_;
};
} // namespace
TEST_F(IncrementalMarkingTest, IncrementalStepDuringAllocation) {
Persistent<Holder> holder =
MakeGarbageCollected<Holder>(GetAllocationHandle());
InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get(),
IncrementalPreciseMarkingConfig);
const HeapObjectHeader* header;
MakeGarbageCollected<GCedWithCallback>(
GetAllocationHandle(), [this, &holder, &header](GCedWithCallback* obj) {
header = &HeapObjectHeader::FromObject(obj);
holder->member_ = obj;
EXPECT_FALSE(header->IsMarked());
FinishSteps(StackState::kMayContainHeapPointers);
EXPECT_FALSE(header->IsMarked());
});
FinishSteps(StackState::kNoHeapPointers);
EXPECT_TRUE(header->IsMarked());
FinishMarking();
}
TEST_F(IncrementalMarkingTest, MarkingRunsOutOfWorkEventually) {
InitializeMarker(*Heap::From(GetHeap()), GetPlatformHandle().get(),
IncrementalPreciseMarkingConfig);
FinishSteps(StackState::kNoHeapPointers);
FinishMarking();
}
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,321 @@
// 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/marking-verifier.h"
#include "include/cppgc/allocation.h"
#include "include/cppgc/member.h"
#include "include/cppgc/persistent.h"
#include "include/cppgc/prefinalizer.h"
#include "src/heap/cppgc/heap-object-header.h"
#include "src/heap/cppgc/heap.h"
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace {
class MarkingVerifierTest : public testing::TestWithHeap {
public:
V8_NOINLINE void VerifyMarking(HeapBase& heap, StackState stack_state,
size_t expected_marked_bytes) {
Heap::From(GetHeap())->object_allocator().ResetLinearAllocationBuffers();
Heap::From(GetHeap())->stack()->SetMarkerAndCallback(
[&heap, stack_state, expected_marked_bytes]() {
MarkingVerifier verifier(heap, CollectionType::kMajor);
verifier.Run(stack_state, expected_marked_bytes);
});
}
};
class GCed : public GarbageCollected<GCed> {
public:
void SetChild(GCed* child) { child_ = child; }
void SetWeakChild(GCed* child) { weak_child_ = child; }
GCed* child() const { return child_.Get(); }
GCed* weak_child() const { return weak_child_.Get(); }
void Trace(cppgc::Visitor* visitor) const {
visitor->Trace(child_);
visitor->Trace(weak_child_);
}
private:
Member<GCed> child_;
WeakMember<GCed> weak_child_;
};
template <typename T>
V8_NOINLINE T access(volatile const T& t) {
return t;
}
bool MarkHeader(HeapObjectHeader& header) {
if (header.TryMarkAtomic()) {
BasePage::FromPayload(&header)->IncrementMarkedBytes(
header.AllocatedSize());
return true;
}
return false;
}
} // namespace
// Following tests should not crash.
TEST_F(MarkingVerifierTest, DoesNotDieOnMarkedOnStackReference) {
GCed* object = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto& header = HeapObjectHeader::FromObject(object);
ASSERT_TRUE(MarkHeader(header));
VerifyMarking(Heap::From(GetHeap())->AsBase(),
StackState::kMayContainHeapPointers, header.AllocatedSize());
access(object);
}
TEST_F(MarkingVerifierTest, DoesNotDieOnMarkedMember) {
Persistent<GCed> parent = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto& parent_header = HeapObjectHeader::FromObject(parent.Get());
ASSERT_TRUE(MarkHeader(parent_header));
parent->SetChild(MakeGarbageCollected<GCed>(GetAllocationHandle()));
auto& child_header = HeapObjectHeader::FromObject(parent->child());
ASSERT_TRUE(MarkHeader(child_header));
VerifyMarking(Heap::From(GetHeap())->AsBase(), StackState::kNoHeapPointers,
parent_header.AllocatedSize() + child_header.AllocatedSize());
}
TEST_F(MarkingVerifierTest, DoesNotDieOnMarkedWeakMember) {
Persistent<GCed> parent = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto& parent_header = HeapObjectHeader::FromObject(parent.Get());
ASSERT_TRUE(MarkHeader(parent_header));
parent->SetWeakChild(MakeGarbageCollected<GCed>(GetAllocationHandle()));
auto& child_header = HeapObjectHeader::FromObject(parent->weak_child());
ASSERT_TRUE(MarkHeader(child_header));
VerifyMarking(Heap::From(GetHeap())->AsBase(), StackState::kNoHeapPointers,
parent_header.AllocatedSize() + child_header.AllocatedSize());
}
namespace {
class GCedWithCallback : public GarbageCollected<GCedWithCallback> {
public:
template <typename Callback>
explicit GCedWithCallback(Callback callback) {
callback(this);
}
void Trace(cppgc::Visitor* visitor) const {}
};
} // namespace
TEST_F(MarkingVerifierTest, DoesNotDieOnInConstructionOnObject) {
MakeGarbageCollected<GCedWithCallback>(
GetAllocationHandle(), [this](GCedWithCallback* obj) {
auto& header = HeapObjectHeader::FromObject(obj);
CHECK(MarkHeader(header));
VerifyMarking(Heap::From(GetHeap())->AsBase(),
StackState::kMayContainHeapPointers,
header.AllocatedSize());
});
}
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());
GCConfig config = GCConfig::PreciseIncrementalConfig();
Heap::From(GetHeap())->StartIncrementalGarbageCollection(config);
MakeGarbageCollected<GCedWithCallbackAndChild>(
GetAllocationHandle(), MakeGarbageCollected<GCed>(GetAllocationHandle()),
[&persistent](GCedWithCallbackAndChild* obj) {
persistent->object = obj;
});
GetMarkerRef()->IncrementalMarkingStepForTesting(StackState::kNoHeapPointers);
Heap::From(GetHeap())->FinalizeIncrementalGarbageCollectionIfRunning(config);
}
// Death tests.
namespace {
class MarkingVerifierDeathTest : public MarkingVerifierTest {
protected:
template <template <typename T> class Reference>
void TestResurrectingPreFinalizer();
};
} // namespace
TEST_F(MarkingVerifierDeathTest, DieOnUnmarkedOnStackReference) {
GCed* object = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto& header = HeapObjectHeader::FromObject(object);
USE(header);
EXPECT_DEATH_IF_SUPPORTED(VerifyMarking(Heap::From(GetHeap())->AsBase(),
StackState::kMayContainHeapPointers,
header.AllocatedSize()),
"");
access(object);
}
TEST_F(MarkingVerifierDeathTest, DieOnUnmarkedMember) {
Persistent<GCed> parent = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto& parent_header = HeapObjectHeader::FromObject(parent);
ASSERT_TRUE(parent_header.TryMarkAtomic());
parent->SetChild(MakeGarbageCollected<GCed>(GetAllocationHandle()));
EXPECT_DEATH_IF_SUPPORTED(
VerifyMarking(Heap::From(GetHeap())->AsBase(),
StackState::kNoHeapPointers, parent_header.AllocatedSize()),
"");
}
TEST_F(MarkingVerifierDeathTest, DieOnUnmarkedWeakMember) {
Persistent<GCed> parent = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto& parent_header = HeapObjectHeader::FromObject(parent);
ASSERT_TRUE(parent_header.TryMarkAtomic());
parent->SetWeakChild(MakeGarbageCollected<GCed>(GetAllocationHandle()));
EXPECT_DEATH_IF_SUPPORTED(
VerifyMarking(Heap::From(GetHeap())->AsBase(),
StackState::kNoHeapPointers, parent_header.AllocatedSize()),
"");
}
#ifdef CPPGC_VERIFY_HEAP
TEST_F(MarkingVerifierDeathTest, DieOnUnexpectedLiveByteCount) {
GCed* object = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto& header = HeapObjectHeader::FromObject(object);
ASSERT_TRUE(header.TryMarkAtomic());
EXPECT_DEATH_IF_SUPPORTED(VerifyMarking(Heap::From(GetHeap())->AsBase(),
StackState::kMayContainHeapPointers,
header.AllocatedSize() - 1),
"");
}
namespace {
void EscapeControlRegexCharacters(std::string& s) {
for (std::string::size_type start_pos = 0;
(start_pos = s.find_first_of("().*+\\", start_pos)) != std::string::npos;
start_pos += 2) {
s.insert(start_pos, "\\");
}
}
} // anonymous namespace
TEST_F(MarkingVerifierDeathTest, DieWithDebugInfoOnUnexpectedLiveByteCount) {
using ::testing::AllOf;
using ::testing::ContainsRegex;
GCed* object = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto& header = HeapObjectHeader::FromObject(object);
ASSERT_TRUE(header.TryMarkAtomic());
size_t allocated = header.AllocatedSize();
size_t expected = allocated - 1;
std::string regex_total =
"\n<--- Mismatch in marking verifier --->"
"\nMarked bytes: expected " +
std::to_string(expected) + " vs. verifier found " +
std::to_string(allocated) + ",";
std::string class_name =
header.GetName(HeapObjectNameForUnnamedObject::kUseClassNameIfSupported)
.value;
EscapeControlRegexCharacters(class_name);
std::string regex_page =
"\nNormal page in space \\d+:"
"\nMarked bytes: expected 0 vs. verifier found " +
std::to_string(allocated) +
",.*"
"\n- " +
class_name + " at .*, size " + std::to_string(header.ObjectSize()) +
", marked\n";
EXPECT_DEATH_IF_SUPPORTED(
VerifyMarking(Heap::From(GetHeap())->AsBase(),
StackState::kNoHeapPointers, expected),
AllOf(ContainsRegex(regex_total), ContainsRegex(regex_page)));
}
#endif // CPPGC_VERIFY_HEAP
namespace {
template <template <typename T> class Reference>
class ResurrectingPreFinalizer
: public GarbageCollected<ResurrectingPreFinalizer<Reference>> {
CPPGC_USING_PRE_FINALIZER(ResurrectingPreFinalizer<Reference>, Dispose);
public:
class Storage : public GarbageCollected<Storage> {
public:
void Trace(Visitor* visitor) const { visitor->Trace(ref); }
Reference<GCed> ref;
};
ResurrectingPreFinalizer(Storage* storage, GCed* object_that_dies)
: storage_(storage), object_that_dies_(object_that_dies) {}
void Trace(Visitor* visitor) const {
visitor->Trace(storage_);
visitor->Trace(object_that_dies_);
}
private:
void Dispose() { storage_->ref = object_that_dies_; }
Member<Storage> storage_;
Member<GCed> object_that_dies_;
};
} // namespace
template <template <typename T> class Reference>
void MarkingVerifierDeathTest::TestResurrectingPreFinalizer() {
Persistent<typename ResurrectingPreFinalizer<Reference>::Storage> storage(
MakeGarbageCollected<
typename ResurrectingPreFinalizer<Reference>::Storage>(
GetAllocationHandle()));
MakeGarbageCollected<ResurrectingPreFinalizer<Reference>>(
GetAllocationHandle(), storage.Get(),
MakeGarbageCollected<GCed>(GetAllocationHandle()));
EXPECT_DEATH_IF_SUPPORTED(PreciseGC(), "");
}
#if CPPGC_VERIFY_HEAP
TEST_F(MarkingVerifierDeathTest, DiesOnResurrectedMember) {
TestResurrectingPreFinalizer<Member>();
}
TEST_F(MarkingVerifierDeathTest, DiesOnResurrectedWeakMember) {
TestResurrectingPreFinalizer<WeakMember>();
}
#endif // CPPGC_VERIFY_HEAP
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,421 @@
// 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/marking-visitor.h"
#include "include/cppgc/allocation.h"
#include "include/cppgc/internal/gc-info.h"
#include "include/cppgc/member.h"
#include "include/cppgc/persistent.h"
#include "include/cppgc/source-location.h"
#include "src/heap/cppgc/globals.h"
#include "src/heap/cppgc/heap-object-header.h"
#include "src/heap/cppgc/marker.h"
#include "src/heap/cppgc/marking-state.h"
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace {
class MarkingVisitorTest : public testing::TestWithHeap {
public:
MarkingVisitorTest()
: marker_(std::make_unique<Marker>(*Heap::From(GetHeap()),
GetPlatformHandle().get())) {
marker_->StartMarking();
}
~MarkingVisitorTest() override { marker_->ClearAllWorklistsForTesting(); }
Marker* GetMarker() { return marker_.get(); }
private:
std::unique_ptr<Marker> marker_;
};
class GCed : public GarbageCollected<GCed> {
public:
void Trace(cppgc::Visitor*) const {}
};
class Mixin : public GarbageCollectedMixin {};
class GCedWithMixin : public GarbageCollected<GCedWithMixin>, public Mixin {
public:
void Trace(cppgc::Visitor*) const override {}
};
class TestMarkingVisitor : public MutatorMarkingVisitor {
public:
explicit TestMarkingVisitor(Marker* marker)
: MutatorMarkingVisitor(marker->heap(),
marker->MutatorMarkingStateForTesting()) {}
~TestMarkingVisitor() { marking_state_.Publish(); }
BasicMarkingState& marking_state() { return marking_state_; }
};
class TestRootMarkingVisitor : public RootMarkingVisitor {
public:
explicit TestRootMarkingVisitor(Marker* marker)
: RootMarkingVisitor(marker->MutatorMarkingStateForTesting()) {}
~TestRootMarkingVisitor() { mutator_marking_state_.Publish(); }
MutatorMarkingState& marking_state() { return mutator_marking_state_; }
};
} // namespace
TEST_F(MarkingVisitorTest, MarkedBytesAreInitiallyZero) {
EXPECT_EQ(0u, GetMarker()->MutatorMarkingStateForTesting().marked_bytes());
}
// Strong references are marked.
TEST_F(MarkingVisitorTest, MarkMember) {
Member<GCed> object(MakeGarbageCollected<GCed>(GetAllocationHandle()));
HeapObjectHeader& header = HeapObjectHeader::FromObject(object);
TestMarkingVisitor visitor(GetMarker());
EXPECT_FALSE(header.IsMarked());
visitor.Trace(object);
EXPECT_TRUE(header.IsMarked());
}
TEST_F(MarkingVisitorTest, MarkMemberMixin) {
GCedWithMixin* object(
MakeGarbageCollected<GCedWithMixin>(GetAllocationHandle()));
Member<Mixin> mixin(object);
HeapObjectHeader& header = HeapObjectHeader::FromObject(object);
TestMarkingVisitor visitor(GetMarker());
EXPECT_FALSE(header.IsMarked());
visitor.Trace(mixin);
EXPECT_TRUE(header.IsMarked());
}
TEST_F(MarkingVisitorTest, MarkPersistent) {
Persistent<GCed> object(MakeGarbageCollected<GCed>(GetAllocationHandle()));
HeapObjectHeader& header = HeapObjectHeader::FromObject(object);
TestRootMarkingVisitor visitor(GetMarker());
EXPECT_FALSE(header.IsMarked());
visitor.Trace(object);
EXPECT_TRUE(header.IsMarked());
}
TEST_F(MarkingVisitorTest, MarkPersistentMixin) {
GCedWithMixin* object(
MakeGarbageCollected<GCedWithMixin>(GetAllocationHandle()));
Persistent<Mixin> mixin(object);
HeapObjectHeader& header = HeapObjectHeader::FromObject(object);
TestRootMarkingVisitor visitor(GetMarker());
EXPECT_FALSE(header.IsMarked());
visitor.Trace(mixin);
EXPECT_TRUE(header.IsMarked());
}
// Weak references are not marked.
TEST_F(MarkingVisitorTest, DontMarkWeakMember) {
WeakMember<GCed> object(MakeGarbageCollected<GCed>(GetAllocationHandle()));
HeapObjectHeader& header = HeapObjectHeader::FromObject(object);
TestMarkingVisitor visitor(GetMarker());
EXPECT_FALSE(header.IsMarked());
visitor.Trace(object);
EXPECT_FALSE(header.IsMarked());
}
TEST_F(MarkingVisitorTest, DontMarkWeakMemberMixin) {
GCedWithMixin* object(
MakeGarbageCollected<GCedWithMixin>(GetAllocationHandle()));
WeakMember<Mixin> mixin(object);
HeapObjectHeader& header = HeapObjectHeader::FromObject(object);
TestMarkingVisitor visitor(GetMarker());
EXPECT_FALSE(header.IsMarked());
visitor.Trace(mixin);
EXPECT_FALSE(header.IsMarked());
}
TEST_F(MarkingVisitorTest, DontMarkWeakPersistent) {
WeakPersistent<GCed> object(
MakeGarbageCollected<GCed>(GetAllocationHandle()));
HeapObjectHeader& header = HeapObjectHeader::FromObject(object);
TestRootMarkingVisitor visitor(GetMarker());
EXPECT_FALSE(header.IsMarked());
visitor.Trace(object);
EXPECT_FALSE(header.IsMarked());
}
TEST_F(MarkingVisitorTest, DontMarkWeakPersistentMixin) {
GCedWithMixin* object(
MakeGarbageCollected<GCedWithMixin>(GetAllocationHandle()));
WeakPersistent<Mixin> mixin(object);
HeapObjectHeader& header = HeapObjectHeader::FromObject(object);
TestRootMarkingVisitor visitor(GetMarker());
EXPECT_FALSE(header.IsMarked());
visitor.Trace(mixin);
EXPECT_FALSE(header.IsMarked());
}
// In construction objects are not marked.
namespace {
class GCedWithInConstructionCallback
: public GarbageCollected<GCedWithInConstructionCallback> {
public:
template <typename Callback>
explicit GCedWithInConstructionCallback(Callback callback) {
callback(this);
}
void Trace(cppgc::Visitor*) const {}
};
class MixinWithInConstructionCallback : public GarbageCollectedMixin {
public:
template <typename Callback>
explicit MixinWithInConstructionCallback(Callback callback) {
callback(this);
}
};
class GCedWithMixinWithInConstructionCallback
: public GarbageCollected<GCedWithMixinWithInConstructionCallback>,
public MixinWithInConstructionCallback {
public:
template <typename Callback>
explicit GCedWithMixinWithInConstructionCallback(Callback callback)
: MixinWithInConstructionCallback(callback) {}
void Trace(cppgc::Visitor*) const override {}
};
} // namespace
TEST_F(MarkingVisitorTest, MarkMemberInConstruction) {
TestMarkingVisitor visitor(GetMarker());
GCedWithInConstructionCallback* gced =
MakeGarbageCollected<GCedWithInConstructionCallback>(
GetAllocationHandle(),
[&visitor](GCedWithInConstructionCallback* obj) {
Member<GCedWithInConstructionCallback> object(obj);
visitor.Trace(object);
});
HeapObjectHeader& header = HeapObjectHeader::FromObject(gced);
EXPECT_TRUE(visitor.marking_state().not_fully_constructed_worklist().Contains(
&header));
EXPECT_FALSE(header.IsMarked());
}
TEST_F(MarkingVisitorTest, MarkMemberMixinInConstruction) {
TestMarkingVisitor visitor(GetMarker());
GCedWithMixinWithInConstructionCallback* gced =
MakeGarbageCollected<GCedWithMixinWithInConstructionCallback>(
GetAllocationHandle(),
[&visitor](MixinWithInConstructionCallback* obj) {
Member<MixinWithInConstructionCallback> mixin(obj);
visitor.Trace(mixin);
});
HeapObjectHeader& header = HeapObjectHeader::FromObject(gced);
EXPECT_TRUE(visitor.marking_state().not_fully_constructed_worklist().Contains(
&header));
EXPECT_FALSE(header.IsMarked());
}
TEST_F(MarkingVisitorTest, DontMarkWeakMemberInConstruction) {
TestMarkingVisitor visitor(GetMarker());
GCedWithInConstructionCallback* gced =
MakeGarbageCollected<GCedWithInConstructionCallback>(
GetAllocationHandle(),
[&visitor](GCedWithInConstructionCallback* obj) {
WeakMember<GCedWithInConstructionCallback> object(obj);
visitor.Trace(object);
});
HeapObjectHeader& header = HeapObjectHeader::FromObject(gced);
EXPECT_FALSE(
visitor.marking_state().not_fully_constructed_worklist().Contains(
&header));
EXPECT_FALSE(header.IsMarked());
}
TEST_F(MarkingVisitorTest, DontMarkWeakMemberMixinInConstruction) {
TestMarkingVisitor visitor(GetMarker());
GCedWithMixinWithInConstructionCallback* gced =
MakeGarbageCollected<GCedWithMixinWithInConstructionCallback>(
GetAllocationHandle(),
[&visitor](MixinWithInConstructionCallback* obj) {
WeakMember<MixinWithInConstructionCallback> mixin(obj);
visitor.Trace(mixin);
});
HeapObjectHeader& header = HeapObjectHeader::FromObject(gced);
EXPECT_FALSE(
visitor.marking_state().not_fully_constructed_worklist().Contains(
&header));
EXPECT_FALSE(header.IsMarked());
}
TEST_F(MarkingVisitorTest, MarkPersistentInConstruction) {
TestRootMarkingVisitor visitor(GetMarker());
GCedWithInConstructionCallback* gced =
MakeGarbageCollected<GCedWithInConstructionCallback>(
GetAllocationHandle(),
[&visitor](GCedWithInConstructionCallback* obj) {
Persistent<GCedWithInConstructionCallback> object(obj);
visitor.Trace(object);
});
HeapObjectHeader& header = HeapObjectHeader::FromObject(gced);
EXPECT_TRUE(visitor.marking_state().not_fully_constructed_worklist().Contains(
&header));
EXPECT_FALSE(header.IsMarked());
}
TEST_F(MarkingVisitorTest, MarkPersistentMixinInConstruction) {
TestRootMarkingVisitor visitor(GetMarker());
GCedWithMixinWithInConstructionCallback* gced =
MakeGarbageCollected<GCedWithMixinWithInConstructionCallback>(
GetAllocationHandle(),
[&visitor](MixinWithInConstructionCallback* obj) {
Persistent<MixinWithInConstructionCallback> mixin(obj);
visitor.Trace(mixin);
});
HeapObjectHeader& header = HeapObjectHeader::FromObject(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::FromObject(object);
TestMarkingVisitor visitor(GetMarker());
EXPECT_FALSE(header.IsMarked());
visitor.TraceStrongly(object);
EXPECT_TRUE(header.IsMarked());
}
namespace {
struct GCedWithDestructor : GarbageCollected<GCedWithDestructor> {
explicit GCedWithDestructor(bool is_child = false) : is_child_(is_child) {}
~GCedWithDestructor() { ++g_finalized; }
static size_t g_finalized;
void Trace(Visitor* v) const;
void TraceAfterDispatch(Visitor* v) const {}
private:
const bool is_child_;
};
size_t GCedWithDestructor::g_finalized = 0;
struct GCedWithInConstructionCallbackWithMember : GCedWithDestructor {
template <typename Callback>
explicit GCedWithInConstructionCallbackWithMember(Callback callback)
: GCedWithDestructor(true) {
callback(this);
}
void TraceAfterDispatch(Visitor* v) const {
GCedWithDestructor::TraceAfterDispatch(v);
v->Trace(member);
}
Member<GCed> member;
};
void GCedWithDestructor::Trace(Visitor* v) const {
if (is_child_) {
static_cast<const GCedWithInConstructionCallbackWithMember*>(this)
->TraceAfterDispatch(v);
} else {
TraceAfterDispatch(v);
}
}
struct ConservativeTracerTest : public testing::TestWithHeap {
ConservativeTracerTest() { GCedWithDestructor::g_finalized = 0; }
};
} // namespace
TEST_F(ConservativeTracerTest, TraceConservativelyInConstructionObject) {
auto* volatile gced =
MakeGarbageCollected<GCedWithInConstructionCallbackWithMember>(
GetAllocationHandle(),
[this](GCedWithInConstructionCallbackWithMember* obj) V8_NOINLINE {
[](GCedWithInConstructionCallbackWithMember* obj,
AllocationHandle& handle) V8_NOINLINE {
obj->member = MakeGarbageCollected<GCed>(handle);
}(obj, GetAllocationHandle());
ConservativeGC();
});
USE(gced);
ConservativeGC();
EXPECT_EQ(0u, GCedWithDestructor::g_finalized);
// Call into HoH::GetGCInfoIndex to prevent the compiler to optimize away the
// stack variable.
EXPECT_EQ(HeapObjectHeader::FromObject(gced).GetGCInfoIndex(),
GCInfoTrait<GCedWithInConstructionCallbackWithMember>::Index());
}
TEST_F(ConservativeTracerTest, TraceConservativelyStack) {
volatile std::array<Member<GCedWithDestructor>, 16u> members =
[this]() V8_NOINLINE {
std::array<Member<GCedWithDestructor>, 16u> members;
for (auto& member : members)
member =
MakeGarbageCollected<GCedWithDestructor>(GetAllocationHandle());
return members;
}();
USE(members);
ConservativeGC();
EXPECT_EQ(0u, GCedWithDestructor::g_finalized);
// Call into HoH::GetGCInfoIndex to prevent the compiler to optimize away the
// stack variable.
auto member =
const_cast<std::remove_volatile_t<decltype(members)>&>(members)[0];
EXPECT_EQ(HeapObjectHeader::FromObject(member.Get()).GetGCInfoIndex(),
GCInfoTrait<GCedWithDestructor>::Index());
}
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,766 @@
// 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/member.h"
#include <algorithm>
#include <vector>
#include "include/cppgc/allocation.h"
#include "include/cppgc/garbage-collected.h"
#include "include/cppgc/internal/member-storage.h"
#include "include/cppgc/internal/pointer-policies.h"
#include "include/cppgc/persistent.h"
#include "include/cppgc/sentinel-pointer.h"
#include "include/cppgc/type-traits.h"
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace {
struct GCed : GarbageCollected<GCed> {
double d;
virtual void Trace(cppgc::Visitor*) const {}
};
struct DerivedMixin : GarbageCollectedMixin {
void Trace(cppgc::Visitor* v) const override {}
};
struct DerivedGCed : GCed, DerivedMixin {
void Trace(cppgc::Visitor* v) const override {
GCed::Trace(v);
DerivedMixin::Trace(v);
}
};
// Compile tests.
static_assert(!IsWeakV<Member<GCed>>, "Member is always strong.");
static_assert(IsWeakV<WeakMember<GCed>>, "WeakMember is always weak.");
static_assert(IsMemberTypeV<Member<GCed>>, "Member must be Member.");
static_assert(IsMemberTypeV<const Member<GCed>>,
"const Member must be Member.");
static_assert(IsMemberTypeV<const Member<GCed>&>,
"const Member ref must be Member.");
static_assert(!IsMemberTypeV<WeakMember<GCed>>,
"WeakMember must not be Member.");
static_assert(!IsMemberTypeV<UntracedMember<GCed>>,
"UntracedMember must not be Member.");
static_assert(!IsMemberTypeV<int>, "int must not be Member.");
static_assert(!IsWeakMemberTypeV<Member<GCed>>,
"Member must not be WeakMember.");
static_assert(IsWeakMemberTypeV<WeakMember<GCed>>,
"WeakMember must be WeakMember.");
static_assert(!IsWeakMemberTypeV<UntracedMember<GCed>>,
"UntracedMember must not be WeakMember.");
static_assert(!IsWeakMemberTypeV<int>, "int must not be WeakMember.");
static_assert(!IsUntracedMemberTypeV<Member<GCed>>,
"Member must not be UntracedMember.");
static_assert(!IsUntracedMemberTypeV<WeakMember<GCed>>,
"WeakMember must not be UntracedMember.");
static_assert(IsUntracedMemberTypeV<UntracedMember<GCed>>,
"UntracedMember must be UntracedMember.");
static_assert(!IsUntracedMemberTypeV<int>, "int must not be UntracedMember.");
static_assert(IsMemberOrWeakMemberTypeV<Member<GCed>>,
"Member must be Member.");
static_assert(IsMemberOrWeakMemberTypeV<WeakMember<GCed>>,
"WeakMember must be WeakMember.");
static_assert(!IsMemberOrWeakMemberTypeV<UntracedMember<GCed>>,
"UntracedMember is neither Member nor WeakMember.");
static_assert(!IsMemberOrWeakMemberTypeV<int>,
"int is neither Member nor WeakMember.");
static_assert(IsAnyMemberTypeV<Member<GCed>>, "Member must be a member type.");
static_assert(IsAnyMemberTypeV<WeakMember<GCed>>,
"WeakMember must be a member type.");
static_assert(IsAnyMemberTypeV<UntracedMember<GCed>>,
"UntracedMember must be a member type.");
static_assert(!IsAnyMemberTypeV<int>, "int must not be a member type.");
static_assert(
IsAnyMemberTypeV<
internal::BasicMember<GCed, class SomeTag, NoWriteBarrierPolicy,
DefaultMemberCheckingPolicy, RawPointer>>,
"Any custom member must be a member type.");
struct CustomWriteBarrierPolicy {
static size_t InitializingWriteBarriersTriggered;
static size_t AssigningWriteBarriersTriggered;
static void InitializingBarrier(const void* slot, const void* value) {
++InitializingWriteBarriersTriggered;
}
template <WriteBarrierSlotType>
static void AssigningBarrier(const void* slot, const void* value) {
++AssigningWriteBarriersTriggered;
}
template <WriteBarrierSlotType>
static void AssigningBarrier(const void* slot, DefaultMemberStorage) {
++AssigningWriteBarriersTriggered;
}
};
size_t CustomWriteBarrierPolicy::InitializingWriteBarriersTriggered = 0;
size_t CustomWriteBarrierPolicy::AssigningWriteBarriersTriggered = 0;
using MemberWithCustomBarrier =
BasicMember<GCed, StrongMemberTag, CustomWriteBarrierPolicy>;
struct CustomCheckingPolicy {
static std::vector<UntracedMember<GCed>> Cached;
static size_t ChecksTriggered;
template <typename T>
void CheckPointer(RawPointer raw_pointer) {
const void* ptr = raw_pointer.Load();
CheckPointer(static_cast<const T*>(ptr));
}
#if defined(CPPGC_POINTER_COMPRESSION)
template <typename T>
void CheckPointer(CompressedPointer compressed_pointer) {
const void* ptr = compressed_pointer.Load();
CheckPointer(static_cast<const T*>(ptr));
}
#endif
template <typename T>
void CheckPointer(const T* ptr) {
EXPECT_NE(Cached.cend(), std::find(Cached.cbegin(), Cached.cend(), ptr));
++ChecksTriggered;
}
};
std::vector<UntracedMember<GCed>> CustomCheckingPolicy::Cached;
size_t CustomCheckingPolicy::ChecksTriggered = 0;
using MemberWithCustomChecking =
BasicMember<GCed, StrongMemberTag, DijkstraWriteBarrierPolicy,
CustomCheckingPolicy>;
class MemberTest : public testing::TestSupportingAllocationOnly {};
} // namespace
template <template <typename> class MemberType>
void EmptyTest() {
{
MemberType<GCed> empty;
EXPECT_EQ(nullptr, empty.Get());
EXPECT_EQ(nullptr, empty.Release());
}
{
MemberType<GCed> empty = nullptr;
EXPECT_EQ(nullptr, empty.Get());
EXPECT_EQ(nullptr, empty.Release());
}
{
// Move-constructs empty from another Member that is created from nullptr.
MemberType<const GCed> empty = nullptr;
EXPECT_EQ(nullptr, empty.Get());
EXPECT_EQ(nullptr, empty.Release());
}
}
TEST_F(MemberTest, Empty) {
EmptyTest<Member>();
EmptyTest<WeakMember>();
EmptyTest<UntracedMember>();
}
template <template <typename> class MemberType>
void AtomicCtorTest(cppgc::Heap* heap) {
{
GCed* gced = MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
MemberType<GCed> member(gced,
typename MemberType<GCed>::AtomicInitializerTag());
EXPECT_EQ(gced, member.Get());
}
{
GCed* gced = MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
MemberType<GCed> member(*gced,
typename MemberType<GCed>::AtomicInitializerTag());
EXPECT_EQ(gced, member.Get());
}
{
MemberType<GCed> member(nullptr,
typename MemberType<GCed>::AtomicInitializerTag());
EXPECT_FALSE(member.Get());
}
{
SentinelPointer s;
MemberType<GCed> member(s,
typename MemberType<GCed>::AtomicInitializerTag());
EXPECT_EQ(s, member.Get());
}
}
TEST_F(MemberTest, AtomicCtor) {
cppgc::Heap* heap = GetHeap();
AtomicCtorTest<Member>(heap);
AtomicCtorTest<WeakMember>(heap);
AtomicCtorTest<UntracedMember>(heap);
}
template <template <typename> class MemberType>
void ClearTest(cppgc::Heap* heap) {
MemberType<GCed> member =
MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
EXPECT_NE(nullptr, member.Get());
member.Clear();
EXPECT_EQ(nullptr, member.Get());
}
TEST_F(MemberTest, Clear) {
cppgc::Heap* heap = GetHeap();
ClearTest<Member>(heap);
ClearTest<WeakMember>(heap);
ClearTest<UntracedMember>(heap);
}
template <template <typename> class MemberType>
void ReleaseTest(cppgc::Heap* heap) {
GCed* gced = MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
MemberType<GCed> member = gced;
EXPECT_NE(nullptr, member.Get());
GCed* raw = member.Release();
EXPECT_EQ(gced, raw);
EXPECT_EQ(nullptr, member.Get());
}
TEST_F(MemberTest, Release) {
cppgc::Heap* heap = GetHeap();
ReleaseTest<Member>(heap);
ReleaseTest<WeakMember>(heap);
ReleaseTest<UntracedMember>(heap);
}
template <template <typename> class MemberType1,
template <typename> class MemberType2>
void SwapTest(cppgc::Heap* heap) {
GCed* gced1 = MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
GCed* gced2 = MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
MemberType1<GCed> member1 = gced1;
MemberType2<GCed> member2 = gced2;
EXPECT_EQ(gced1, member1.Get());
EXPECT_EQ(gced2, member2.Get());
member1.Swap(member2);
EXPECT_EQ(gced2, member1.Get());
EXPECT_EQ(gced1, member2.Get());
}
TEST_F(MemberTest, Swap) {
cppgc::Heap* heap = GetHeap();
SwapTest<Member, Member>(heap);
SwapTest<Member, WeakMember>(heap);
SwapTest<Member, UntracedMember>(heap);
SwapTest<WeakMember, Member>(heap);
SwapTest<WeakMember, WeakMember>(heap);
SwapTest<WeakMember, UntracedMember>(heap);
SwapTest<UntracedMember, Member>(heap);
SwapTest<UntracedMember, WeakMember>(heap);
SwapTest<UntracedMember, UntracedMember>(heap);
}
template <template <typename> class MemberType1,
template <typename> class MemberType2>
void MoveTest(cppgc::Heap* heap) {
{
GCed* gced1 = MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
MemberType1<GCed> member1 = gced1;
MemberType2<GCed> member2(std::move(member1));
// Move-from member must be in empty state.
EXPECT_FALSE(member1);
EXPECT_EQ(gced1, member2.Get());
}
{
GCed* gced1 = MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
MemberType1<GCed> member1 = gced1;
MemberType2<GCed> member2;
member2 = std::move(member1);
// Move-from member must be in empty state.
EXPECT_FALSE(member1);
EXPECT_EQ(gced1, member2.Get());
}
}
TEST_F(MemberTest, Move) {
cppgc::Heap* heap = GetHeap();
MoveTest<Member, Member>(heap);
MoveTest<Member, WeakMember>(heap);
MoveTest<Member, UntracedMember>(heap);
MoveTest<WeakMember, Member>(heap);
MoveTest<WeakMember, WeakMember>(heap);
MoveTest<WeakMember, UntracedMember>(heap);
MoveTest<UntracedMember, Member>(heap);
MoveTest<UntracedMember, WeakMember>(heap);
MoveTest<UntracedMember, UntracedMember>(heap);
}
template <template <typename> class MemberType1,
template <typename> class MemberType2>
void HeterogeneousConversionTest(cppgc::Heap* heap) {
{
MemberType1<GCed> member1 =
MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
MemberType2<GCed> member2 = member1;
EXPECT_EQ(member1.Get(), member2.Get());
}
{
MemberType1<DerivedGCed> member1 =
MakeGarbageCollected<DerivedGCed>(heap->GetAllocationHandle());
MemberType2<GCed> member2 = member1;
EXPECT_EQ(member1.Get(), member2.Get());
}
{
MemberType1<GCed> member1 =
MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
MemberType2<GCed> member2;
member2 = member1;
EXPECT_EQ(member1.Get(), member2.Get());
}
{
MemberType1<DerivedGCed> member1 =
MakeGarbageCollected<DerivedGCed>(heap->GetAllocationHandle());
MemberType2<GCed> member2;
member2 = member1;
EXPECT_EQ(member1.Get(), member2.Get());
}
}
TEST_F(MemberTest, HeterogeneousInterface) {
cppgc::Heap* heap = GetHeap();
HeterogeneousConversionTest<Member, Member>(heap);
HeterogeneousConversionTest<Member, WeakMember>(heap);
HeterogeneousConversionTest<Member, UntracedMember>(heap);
HeterogeneousConversionTest<WeakMember, Member>(heap);
HeterogeneousConversionTest<WeakMember, WeakMember>(heap);
HeterogeneousConversionTest<WeakMember, UntracedMember>(heap);
HeterogeneousConversionTest<UntracedMember, Member>(heap);
HeterogeneousConversionTest<UntracedMember, WeakMember>(heap);
HeterogeneousConversionTest<UntracedMember, UntracedMember>(heap);
}
template <template <typename> class MemberType,
template <typename> class PersistentType>
void PersistentConversionTest(cppgc::Heap* heap) {
{
PersistentType<GCed> persistent =
MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
MemberType<GCed> member = persistent;
EXPECT_EQ(persistent.Get(), member.Get());
}
{
PersistentType<DerivedGCed> persistent =
MakeGarbageCollected<DerivedGCed>(heap->GetAllocationHandle());
MemberType<GCed> member = persistent;
EXPECT_EQ(persistent.Get(), member.Get());
}
{
PersistentType<GCed> persistent =
MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
MemberType<GCed> member;
member = persistent;
EXPECT_EQ(persistent.Get(), member.Get());
}
{
PersistentType<DerivedGCed> persistent =
MakeGarbageCollected<DerivedGCed>(heap->GetAllocationHandle());
MemberType<GCed> member;
member = persistent;
EXPECT_EQ(persistent.Get(), member.Get());
}
}
TEST_F(MemberTest, PersistentConversion) {
cppgc::Heap* heap = GetHeap();
PersistentConversionTest<Member, Persistent>(heap);
PersistentConversionTest<Member, WeakPersistent>(heap);
PersistentConversionTest<WeakMember, Persistent>(heap);
PersistentConversionTest<WeakMember, WeakPersistent>(heap);
PersistentConversionTest<UntracedMember, Persistent>(heap);
PersistentConversionTest<UntracedMember, WeakPersistent>(heap);
}
template <template <typename> class MemberType1,
template <typename> class MemberType2>
void EqualityTest(cppgc::Heap* heap) {
{
GCed* gced = MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
MemberType1<GCed> member1 = gced;
MemberType2<GCed> member2 = gced;
EXPECT_TRUE(member1 == member2);
EXPECT_TRUE(member1 == gced);
EXPECT_TRUE(member2 == gced);
EXPECT_FALSE(member1 != member2);
EXPECT_FALSE(member1 != gced);
EXPECT_FALSE(member2 != gced);
member2 = member1;
EXPECT_TRUE(member1 == member2);
EXPECT_TRUE(member1 == gced);
EXPECT_TRUE(member2 == gced);
EXPECT_FALSE(member1 != member2);
EXPECT_FALSE(member1 != gced);
EXPECT_FALSE(member2 != gced);
}
{
MemberType1<GCed> member1 =
MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
MemberType2<GCed> member2 =
MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
EXPECT_TRUE(member1 != member2);
EXPECT_TRUE(member1 != member2.Get());
EXPECT_FALSE(member1 == member2);
EXPECT_FALSE(member1 == member2.Get());
}
}
TEST_F(MemberTest, EqualityTest) {
cppgc::Heap* heap = GetHeap();
EqualityTest<Member, Member>(heap);
EqualityTest<Member, WeakMember>(heap);
EqualityTest<Member, UntracedMember>(heap);
EqualityTest<WeakMember, Member>(heap);
EqualityTest<WeakMember, WeakMember>(heap);
EqualityTest<WeakMember, UntracedMember>(heap);
EqualityTest<UntracedMember, Member>(heap);
EqualityTest<UntracedMember, WeakMember>(heap);
EqualityTest<UntracedMember, UntracedMember>(heap);
}
TEST_F(MemberTest, HeterogeneousEqualityTest) {
cppgc::Heap* heap = GetHeap();
{
auto* gced = MakeGarbageCollected<DerivedGCed>(heap->GetAllocationHandle());
auto* derived = static_cast<DerivedMixin*>(gced);
ASSERT_NE(reinterpret_cast<void*>(gced), reinterpret_cast<void*>(derived));
}
{
auto* gced = MakeGarbageCollected<DerivedGCed>(heap->GetAllocationHandle());
Member<DerivedGCed> member = gced;
#define EXPECT_MIXIN_EQUAL(Mixin) \
EXPECT_TRUE(member == mixin); \
EXPECT_TRUE(member == gced); \
EXPECT_TRUE(mixin == gced); \
EXPECT_FALSE(member != mixin); \
EXPECT_FALSE(member != gced); \
EXPECT_FALSE(mixin != gced);
{
// Construct from raw.
Member<DerivedMixin> mixin = gced;
EXPECT_MIXIN_EQUAL(mixin);
}
{
// Copy construct from member.
Member<DerivedMixin> mixin = member;
EXPECT_MIXIN_EQUAL(mixin);
}
{
// Move construct from member.
Member<DerivedMixin> mixin = std::move(member);
member = gced;
EXPECT_MIXIN_EQUAL(mixin);
}
{
// Copy assign from member.
Member<DerivedMixin> mixin;
mixin = member;
EXPECT_MIXIN_EQUAL(mixin);
}
{
// Move assign from member.
Member<DerivedMixin> mixin;
mixin = std::move(member);
member = gced;
EXPECT_MIXIN_EQUAL(mixin);
}
#undef EXPECT_MIXIN_EQUAL
}
}
TEST_F(MemberTest, WriteBarrierTriggered) {
CustomWriteBarrierPolicy::InitializingWriteBarriersTriggered = 0;
CustomWriteBarrierPolicy::AssigningWriteBarriersTriggered = 0;
GCed* gced = MakeGarbageCollected<GCed>(GetAllocationHandle());
MemberWithCustomBarrier member1 = gced;
EXPECT_EQ(1u, CustomWriteBarrierPolicy::InitializingWriteBarriersTriggered);
EXPECT_EQ(0u, CustomWriteBarrierPolicy::AssigningWriteBarriersTriggered);
member1 = gced;
EXPECT_EQ(1u, CustomWriteBarrierPolicy::InitializingWriteBarriersTriggered);
EXPECT_EQ(1u, CustomWriteBarrierPolicy::AssigningWriteBarriersTriggered);
member1 = nullptr;
EXPECT_EQ(1u, CustomWriteBarrierPolicy::InitializingWriteBarriersTriggered);
EXPECT_EQ(1u, CustomWriteBarrierPolicy::AssigningWriteBarriersTriggered);
MemberWithCustomBarrier member2 = nullptr;
// No initializing barriers for std::nullptr_t.
EXPECT_EQ(1u, CustomWriteBarrierPolicy::InitializingWriteBarriersTriggered);
EXPECT_EQ(1u, CustomWriteBarrierPolicy::AssigningWriteBarriersTriggered);
member2 = kSentinelPointer;
EXPECT_EQ(kSentinelPointer, member2.Get());
EXPECT_EQ(kSentinelPointer, member2);
// No initializing barriers for pointer sentinel.
EXPECT_EQ(1u, CustomWriteBarrierPolicy::InitializingWriteBarriersTriggered);
EXPECT_EQ(1u, CustomWriteBarrierPolicy::AssigningWriteBarriersTriggered);
member2.Swap(member1);
EXPECT_EQ(3u, CustomWriteBarrierPolicy::AssigningWriteBarriersTriggered);
}
TEST_F(MemberTest, CheckingPolicy) {
static constexpr size_t kElements = 64u;
CustomCheckingPolicy::ChecksTriggered = 0u;
for (std::size_t i = 0; i < kElements; ++i) {
CustomCheckingPolicy::Cached.push_back(
MakeGarbageCollected<GCed>(GetAllocationHandle()));
}
MemberWithCustomChecking member;
for (GCed* item : CustomCheckingPolicy::Cached) {
member = item;
}
EXPECT_EQ(CustomCheckingPolicy::Cached.size(),
CustomCheckingPolicy::ChecksTriggered);
}
namespace {
class MemberHeapTest : public testing::TestWithHeap {};
class GCedWithMembers final : public GarbageCollected<GCedWithMembers> {
public:
static size_t live_count_;
GCedWithMembers() : GCedWithMembers(nullptr, nullptr) {}
explicit GCedWithMembers(GCedWithMembers* strong, GCedWithMembers* weak)
: strong_nested_(strong), weak_nested_(weak) {
++live_count_;
}
~GCedWithMembers() { --live_count_; }
void Trace(cppgc::Visitor* visitor) const {
visitor->Trace(strong_nested_);
visitor->Trace(weak_nested_);
}
bool WasNestedCleared() const { return !weak_nested_; }
private:
Member<GCedWithMembers> strong_nested_;
WeakMember<GCedWithMembers> weak_nested_;
};
size_t GCedWithMembers::live_count_ = 0;
} // namespace
TEST_F(MemberHeapTest, MemberRetainsObject) {
EXPECT_EQ(0u, GCedWithMembers::live_count_);
{
GCedWithMembers* nested_object =
MakeGarbageCollected<GCedWithMembers>(GetAllocationHandle());
Persistent<GCedWithMembers> gced_with_members =
MakeGarbageCollected<GCedWithMembers>(GetAllocationHandle(),
nested_object, nested_object);
EXPECT_EQ(2u, GCedWithMembers::live_count_);
PreciseGC();
EXPECT_EQ(2u, GCedWithMembers::live_count_);
EXPECT_FALSE(gced_with_members->WasNestedCleared());
}
PreciseGC();
EXPECT_EQ(0u, GCedWithMembers::live_count_);
{
GCedWithMembers* nested_object =
MakeGarbageCollected<GCedWithMembers>(GetAllocationHandle());
GCedWithMembers* gced_with_members = MakeGarbageCollected<GCedWithMembers>(
GetAllocationHandle(), nested_object, nested_object);
EXPECT_EQ(2u, GCedWithMembers::live_count_);
ConservativeGC();
EXPECT_EQ(2u, GCedWithMembers::live_count_);
EXPECT_FALSE(gced_with_members->WasNestedCleared());
}
PreciseGC();
EXPECT_EQ(0u, GCedWithMembers::live_count_);
}
TEST_F(MemberHeapTest, WeakMemberDoesNotRetainObject) {
EXPECT_EQ(0u, GCedWithMembers::live_count_);
auto* weak_nested =
MakeGarbageCollected<GCedWithMembers>(GetAllocationHandle());
Persistent<GCedWithMembers> gced_with_members(
MakeGarbageCollected<GCedWithMembers>(GetAllocationHandle(), nullptr,
weak_nested));
PreciseGC();
EXPECT_EQ(1u, GCedWithMembers::live_count_);
EXPECT_TRUE(gced_with_members->WasNestedCleared());
}
namespace {
class GCedWithConstWeakMember
: public GarbageCollected<GCedWithConstWeakMember> {
public:
explicit GCedWithConstWeakMember(const GCedWithMembers* weak)
: weak_member_(weak) {}
void Trace(Visitor* visitor) const { visitor->Trace(weak_member_); }
const GCedWithMembers* weak_member() const { return weak_member_; }
private:
const WeakMember<const GCedWithMembers> weak_member_;
};
} // namespace
TEST_F(MemberHeapTest, ConstWeakRefIsClearedOnGC) {
const WeakPersistent<const GCedWithMembers> weak_persistent =
MakeGarbageCollected<GCedWithMembers>(GetAllocationHandle());
Persistent<GCedWithConstWeakMember> persistent =
MakeGarbageCollected<GCedWithConstWeakMember>(GetAllocationHandle(),
weak_persistent);
PreciseGC();
EXPECT_FALSE(weak_persistent);
EXPECT_FALSE(persistent->weak_member());
}
#if V8_ENABLE_CHECKS
namespace {
class MemberHeapDeathTest : public testing::TestWithHeap {};
class LinkedNode final : public GarbageCollected<LinkedNode> {
public:
explicit LinkedNode(LinkedNode* next) : next_(next) {}
void Trace(Visitor* v) const { v->Trace(next_); }
void SetNext(LinkedNode* next) { next_ = next; }
private:
Member<LinkedNode> next_;
};
} // namespace
// The following tests create multiple heaps per thread, which is not supported
// with pointer compression enabled.
#if !defined(CPPGC_POINTER_COMPRESSION) && defined(ENABLE_SLOW_DCHECKS)
TEST_F(MemberHeapDeathTest, CheckForOffHeapMemberCrashesOnReassignment) {
std::vector<UntracedMember<LinkedNode>> off_heap_member;
// Verification state is constructed on first assignment.
off_heap_member.emplace_back(
MakeGarbageCollected<LinkedNode>(GetAllocationHandle(), nullptr));
{
auto tmp_heap = cppgc::Heap::Create(platform_);
auto* tmp_obj = MakeGarbageCollected<LinkedNode>(
tmp_heap->GetAllocationHandle(), nullptr);
EXPECT_DEATH_IF_SUPPORTED(off_heap_member[0] = tmp_obj, "");
}
}
TEST_F(MemberHeapDeathTest, CheckForOnStackMemberCrashesOnReassignment) {
Member<LinkedNode> stack_member;
// Verification state is constructed on first assignment.
stack_member =
MakeGarbageCollected<LinkedNode>(GetAllocationHandle(), nullptr);
{
auto tmp_heap = cppgc::Heap::Create(platform_);
auto* tmp_obj = MakeGarbageCollected<LinkedNode>(
tmp_heap->GetAllocationHandle(), nullptr);
EXPECT_DEATH_IF_SUPPORTED(stack_member = tmp_obj, "");
}
}
TEST_F(MemberHeapDeathTest, CheckForOnHeapMemberCrashesOnInitialAssignment) {
auto* obj = MakeGarbageCollected<LinkedNode>(GetAllocationHandle(), nullptr);
{
auto tmp_heap = cppgc::Heap::Create(platform_);
EXPECT_DEATH_IF_SUPPORTED(
// For regular on-heap Member references the verification state is
// constructed eagerly on creating the reference.
MakeGarbageCollected<LinkedNode>(tmp_heap->GetAllocationHandle(), obj),
"");
}
}
#endif // defined(CPPGC_POINTER_COMPRESSION) && defined(ENABLE_SLOW_DCHECKS)
#if defined(CPPGC_POINTER_COMPRESSION)
TEST_F(MemberTest, CompressDecompress) {
CompressedPointer cp;
EXPECT_EQ(nullptr, cp.Load());
Member<GCed> member;
cp.Store(member.Get());
EXPECT_EQ(nullptr, cp.Load());
cp.Store(kSentinelPointer);
EXPECT_EQ(kSentinelPointer, cp.Load());
member = kSentinelPointer;
cp.Store(member.Get());
EXPECT_EQ(kSentinelPointer, cp.Load());
member = MakeGarbageCollected<GCed>(GetAllocationHandle());
cp.Store(member.Get());
EXPECT_EQ(member.Get(), cp.Load());
}
#endif // defined(CPPGC_POINTER_COMPRESSION)
#endif // V8_ENABLE_CHECKS
#if defined(CPPGC_CAGED_HEAP)
TEST_F(MemberTest, CompressedPointerFindCandidates) {
auto try_find = [](const void* candidate, const void* needle) {
bool found = false;
CompressedPointer::VisitPossiblePointers(
needle, [candidate, &found](const void* address) {
if (candidate == address) {
found = true;
}
});
return found;
};
auto compress_in_lower_halfword = [](const void* address) {
return reinterpret_cast<void*>(
static_cast<uintptr_t>(CompressedPointer::Compress(address)));
};
auto compress_in_upper_halfword = [](const void* address) {
return reinterpret_cast<void*>(
static_cast<uintptr_t>(CompressedPointer::Compress(address))
<< (sizeof(CompressedPointer::IntegralType) * CHAR_BIT));
};
auto decompress_partially = [](const void* address) {
return reinterpret_cast<void*>(
static_cast<uintptr_t>(CompressedPointer::Compress(address))
<< api_constants::kPointerCompressionShift);
};
const uintptr_t base = CagedHeapBase::GetBase();
// There's at least one page that is not used in the beginning of the cage.
static constexpr auto kAssumedCageRedZone = kPageSize;
const auto begin_needle = reinterpret_cast<void*>(base + kAssumedCageRedZone);
EXPECT_TRUE(try_find(begin_needle, begin_needle));
EXPECT_TRUE(try_find(begin_needle, compress_in_lower_halfword(begin_needle)));
EXPECT_TRUE(try_find(begin_needle, compress_in_upper_halfword(begin_needle)));
EXPECT_TRUE(try_find(begin_needle, decompress_partially(begin_needle)));
static constexpr auto kReservationSize =
api_constants::kCagedHeapMaxReservationSize;
static_assert(kReservationSize % kAllocationGranularity == 0);
const auto end_needle =
reinterpret_cast<void*>(base + kReservationSize - kAllocationGranularity);
EXPECT_TRUE(try_find(end_needle, end_needle));
EXPECT_TRUE(try_find(end_needle, compress_in_lower_halfword(end_needle)));
EXPECT_TRUE(try_find(end_needle, compress_in_upper_halfword(end_needle)));
EXPECT_TRUE(try_find(end_needle, decompress_partially(end_needle)));
static constexpr auto kMidOffset = kReservationSize / 2;
static_assert(kMidOffset % kAllocationGranularity == 0);
const auto mid_needle = reinterpret_cast<void*>(base + kMidOffset);
EXPECT_TRUE(try_find(mid_needle, mid_needle));
EXPECT_TRUE(try_find(mid_needle, compress_in_lower_halfword(mid_needle)));
EXPECT_TRUE(try_find(mid_needle, compress_in_upper_halfword(mid_needle)));
EXPECT_TRUE(try_find(mid_needle, decompress_partially(mid_needle)));
}
#endif // defined(CPPGC_CAGED_HEAP)
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,319 @@
// 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/metric-recorder.h"
#include "src/heap/cppgc/stats-collector.h"
#include "test/unittests/heap/cppgc/tests.h"
namespace cppgc {
namespace internal {
namespace {
class MetricRecorderImpl final : public MetricRecorder {
public:
void AddMainThreadEvent(const GCCycle& event) final {
GCCycle_event = event;
GCCycle_callcount++;
}
void AddMainThreadEvent(const MainThreadIncrementalMark& event) final {
MainThreadIncrementalMark_event = event;
MainThreadIncrementalMark_callcount++;
}
void AddMainThreadEvent(const MainThreadIncrementalSweep& event) final {
MainThreadIncrementalSweep_event = event;
MainThreadIncrementalSweep_callcount++;
}
static size_t GCCycle_callcount;
static GCCycle GCCycle_event;
static size_t MainThreadIncrementalMark_callcount;
static MainThreadIncrementalMark MainThreadIncrementalMark_event;
static size_t MainThreadIncrementalSweep_callcount;
static MainThreadIncrementalSweep MainThreadIncrementalSweep_event;
};
// static
size_t MetricRecorderImpl::GCCycle_callcount = 0u;
MetricRecorderImpl::GCCycle MetricRecorderImpl::GCCycle_event;
size_t MetricRecorderImpl::MainThreadIncrementalMark_callcount = 0u;
MetricRecorderImpl::MainThreadIncrementalMark
MetricRecorderImpl::MainThreadIncrementalMark_event;
size_t MetricRecorderImpl::MainThreadIncrementalSweep_callcount = 0u;
MetricRecorderImpl::MainThreadIncrementalSweep
MetricRecorderImpl::MainThreadIncrementalSweep_event;
class MetricRecorderTest : public testing::TestWithHeap {
public:
MetricRecorderTest() : stats(Heap::From(GetHeap())->stats_collector()) {
stats->SetMetricRecorder(std::make_unique<MetricRecorderImpl>());
}
void StartGC() {
stats->NotifyMarkingStarted(CollectionType::kMajor,
GCConfig::MarkingType::kIncremental,
GCConfig::IsForcedGC::kNotForced);
}
void EndGC(size_t marked_bytes) {
stats->NotifyMarkingCompleted(marked_bytes);
stats->NotifySweepingCompleted(GCConfig::SweepingType::kIncremental);
}
StatsCollector* stats;
};
} // namespace
TEST_F(MetricRecorderTest, IncrementalScopesReportedImmediately) {
MetricRecorderImpl::GCCycle_callcount = 0u;
MetricRecorderImpl::MainThreadIncrementalMark_callcount = 0u;
MetricRecorderImpl::MainThreadIncrementalSweep_callcount = 0u;
StartGC();
{
EXPECT_EQ(0u, MetricRecorderImpl::MainThreadIncrementalMark_callcount);
{
StatsCollector::EnabledScope scope(
Heap::From(GetHeap())->stats_collector(),
StatsCollector::kIncrementalMark);
scope.DecreaseStartTimeForTesting(
v8::base::TimeDelta::FromMilliseconds(1));
}
EXPECT_EQ(1u, MetricRecorderImpl::MainThreadIncrementalMark_callcount);
EXPECT_LT(0u,
MetricRecorderImpl::MainThreadIncrementalMark_event.duration_us);
}
{
EXPECT_EQ(0u, MetricRecorderImpl::MainThreadIncrementalSweep_callcount);
{
StatsCollector::EnabledScope scope(
Heap::From(GetHeap())->stats_collector(),
StatsCollector::kIncrementalSweep);
scope.DecreaseStartTimeForTesting(
v8::base::TimeDelta::FromMilliseconds(1));
}
EXPECT_EQ(1u, MetricRecorderImpl::MainThreadIncrementalSweep_callcount);
EXPECT_LT(0u,
MetricRecorderImpl::MainThreadIncrementalSweep_event.duration_us);
}
EXPECT_EQ(0u, MetricRecorderImpl::GCCycle_callcount);
EndGC(0);
}
TEST_F(MetricRecorderTest, NonIncrementalScopesNotReportedImmediately) {
MetricRecorderImpl::GCCycle_callcount = 0u;
MetricRecorderImpl::MainThreadIncrementalMark_callcount = 0u;
MetricRecorderImpl::MainThreadIncrementalSweep_callcount = 0u;
StartGC();
{
StatsCollector::EnabledScope scope(Heap::From(GetHeap())->stats_collector(),
StatsCollector::kAtomicMark);
}
{
StatsCollector::EnabledScope scope(Heap::From(GetHeap())->stats_collector(),
StatsCollector::kAtomicWeak);
}
{
StatsCollector::EnabledScope scope(Heap::From(GetHeap())->stats_collector(),
StatsCollector::kAtomicCompact);
}
{
StatsCollector::EnabledScope scope(Heap::From(GetHeap())->stats_collector(),
StatsCollector::kAtomicSweep);
}
{
StatsCollector::EnabledConcurrentScope scope(
Heap::From(GetHeap())->stats_collector(),
StatsCollector::kConcurrentMark);
}
{
StatsCollector::EnabledConcurrentScope scope(
Heap::From(GetHeap())->stats_collector(),
StatsCollector::kConcurrentSweep);
}
EXPECT_EQ(0u, MetricRecorderImpl::MainThreadIncrementalMark_callcount);
EXPECT_EQ(0u, MetricRecorderImpl::MainThreadIncrementalSweep_callcount);
EXPECT_EQ(0u, MetricRecorderImpl::GCCycle_callcount);
EndGC(0);
}
TEST_F(MetricRecorderTest, CycleEndMetricsReportedOnGcEnd) {
MetricRecorderImpl::GCCycle_callcount = 0u;
MetricRecorderImpl::MainThreadIncrementalMark_callcount = 0u;
MetricRecorderImpl::MainThreadIncrementalSweep_callcount = 0u;
StartGC();
EndGC(0);
EXPECT_EQ(0u, MetricRecorderImpl::MainThreadIncrementalMark_callcount);
EXPECT_EQ(0u, MetricRecorderImpl::MainThreadIncrementalSweep_callcount);
EXPECT_EQ(1u, MetricRecorderImpl::GCCycle_callcount);
}
TEST_F(MetricRecorderTest, CycleEndHistogramReportsCorrectValues) {
StartGC();
{
// Warmup scope to make sure everything is loaded in memory and reduce noise
// in timing measurements.
StatsCollector::EnabledScope scope(Heap::From(GetHeap())->stats_collector(),
StatsCollector::kIncrementalMark);
}
EndGC(1000);
StartGC();
{
StatsCollector::EnabledScope scope(Heap::From(GetHeap())->stats_collector(),
StatsCollector::kIncrementalMark);
scope.DecreaseStartTimeForTesting(
v8::base::TimeDelta::FromMilliseconds(10));
}
{
StatsCollector::EnabledScope scope(Heap::From(GetHeap())->stats_collector(),
StatsCollector::kIncrementalSweep);
scope.DecreaseStartTimeForTesting(
v8::base::TimeDelta::FromMilliseconds(20));
}
{
StatsCollector::EnabledScope scope(Heap::From(GetHeap())->stats_collector(),
StatsCollector::kAtomicMark);
scope.DecreaseStartTimeForTesting(
v8::base::TimeDelta::FromMilliseconds(30));
}
{
StatsCollector::EnabledScope scope(Heap::From(GetHeap())->stats_collector(),
StatsCollector::kAtomicWeak);
scope.DecreaseStartTimeForTesting(
v8::base::TimeDelta::FromMilliseconds(50));
}
{
StatsCollector::EnabledScope scope(Heap::From(GetHeap())->stats_collector(),
StatsCollector::kAtomicCompact);
scope.DecreaseStartTimeForTesting(
v8::base::TimeDelta::FromMilliseconds(60));
}
{
StatsCollector::EnabledScope scope(Heap::From(GetHeap())->stats_collector(),
StatsCollector::kAtomicSweep);
scope.DecreaseStartTimeForTesting(
v8::base::TimeDelta::FromMilliseconds(70));
}
{
StatsCollector::EnabledConcurrentScope scope(
Heap::From(GetHeap())->stats_collector(),
StatsCollector::kConcurrentMark);
scope.DecreaseStartTimeForTesting(
v8::base::TimeDelta::FromMilliseconds(80));
}
{
StatsCollector::EnabledConcurrentScope scope(
Heap::From(GetHeap())->stats_collector(),
StatsCollector::kConcurrentSweep);
scope.DecreaseStartTimeForTesting(
v8::base::TimeDelta::FromMilliseconds(100));
}
EndGC(300);
// Check durations.
static constexpr int64_t kDurationComparisonTolerance = 5000;
EXPECT_LT(std::abs(MetricRecorderImpl::GCCycle_event.main_thread_incremental
.mark_duration_us -
10000),
kDurationComparisonTolerance);
EXPECT_LT(std::abs(MetricRecorderImpl::GCCycle_event.main_thread_incremental
.sweep_duration_us -
20000),
kDurationComparisonTolerance);
EXPECT_LT(std::abs(MetricRecorderImpl::GCCycle_event.main_thread_atomic
.mark_duration_us -
30000),
kDurationComparisonTolerance);
EXPECT_LT(std::abs(MetricRecorderImpl::GCCycle_event.main_thread_atomic
.weak_duration_us -
50000),
kDurationComparisonTolerance);
EXPECT_LT(std::abs(MetricRecorderImpl::GCCycle_event.main_thread_atomic
.compact_duration_us -
60000),
kDurationComparisonTolerance);
EXPECT_LT(std::abs(MetricRecorderImpl::GCCycle_event.main_thread_atomic
.sweep_duration_us -
70000),
kDurationComparisonTolerance);
EXPECT_LT(
std::abs(MetricRecorderImpl::GCCycle_event.main_thread.mark_duration_us -
40000),
kDurationComparisonTolerance);
EXPECT_LT(
std::abs(MetricRecorderImpl::GCCycle_event.main_thread.weak_duration_us -
50000),
kDurationComparisonTolerance);
EXPECT_LT(
std::abs(
MetricRecorderImpl::GCCycle_event.main_thread.compact_duration_us -
60000),
kDurationComparisonTolerance);
EXPECT_LT(
std::abs(MetricRecorderImpl::GCCycle_event.main_thread.sweep_duration_us -
90000),
kDurationComparisonTolerance);
EXPECT_LT(std::abs(MetricRecorderImpl::GCCycle_event.total.mark_duration_us -
120000),
kDurationComparisonTolerance);
EXPECT_LT(std::abs(MetricRecorderImpl::GCCycle_event.total.weak_duration_us -
50000),
kDurationComparisonTolerance);
EXPECT_LT(
std::abs(MetricRecorderImpl::GCCycle_event.total.compact_duration_us -
60000),
kDurationComparisonTolerance);
EXPECT_LT(std::abs(MetricRecorderImpl::GCCycle_event.total.sweep_duration_us -
190000),
kDurationComparisonTolerance);
// Check collection rate and efficiency.
EXPECT_DOUBLE_EQ(
0.7, MetricRecorderImpl::GCCycle_event.collection_rate_in_percent);
static constexpr double kEfficiencyComparisonTolerance = 0.0005;
EXPECT_LT(
std::abs(MetricRecorderImpl::GCCycle_event.efficiency_in_bytes_per_us -
(700.0 / (120000 + 50000 + 60000 + 190000))),
kEfficiencyComparisonTolerance);
EXPECT_LT(std::abs(MetricRecorderImpl::GCCycle_event
.main_thread_efficiency_in_bytes_per_us -
(700.0 / (40000 + 50000 + 60000 + 90000))),
kEfficiencyComparisonTolerance);
}
TEST_F(MetricRecorderTest, ObjectSizeMetricsNoAllocations) {
// Populate previous event.
StartGC();
EndGC(1000);
// Populate current event.
StartGC();
EndGC(800);
EXPECT_EQ(1000u, MetricRecorderImpl::GCCycle_event.objects.before_bytes);
EXPECT_EQ(800u, MetricRecorderImpl::GCCycle_event.objects.after_bytes);
EXPECT_EQ(200u, MetricRecorderImpl::GCCycle_event.objects.freed_bytes);
EXPECT_EQ(0u, MetricRecorderImpl::GCCycle_event.memory.before_bytes);
EXPECT_EQ(0u, MetricRecorderImpl::GCCycle_event.memory.after_bytes);
EXPECT_EQ(0u, MetricRecorderImpl::GCCycle_event.memory.freed_bytes);
}
TEST_F(MetricRecorderTest, ObjectSizeMetricsWithAllocations) {
// Populate previous event.
StartGC();
EndGC(1000);
// Populate current event.
StartGC();
stats->NotifyAllocation(300);
stats->NotifyAllocatedMemory(1400);
stats->NotifyFreedMemory(700);
stats->NotifyMarkingCompleted(800);
stats->NotifyAllocation(150);
stats->NotifyAllocatedMemory(1000);
stats->NotifyFreedMemory(400);
stats->NotifySweepingCompleted(GCConfig::SweepingType::kAtomic);
EXPECT_EQ(1300u, MetricRecorderImpl::GCCycle_event.objects.before_bytes);
EXPECT_EQ(800, MetricRecorderImpl::GCCycle_event.objects.after_bytes);
EXPECT_EQ(500u, MetricRecorderImpl::GCCycle_event.objects.freed_bytes);
EXPECT_EQ(700u, MetricRecorderImpl::GCCycle_event.memory.before_bytes);
EXPECT_EQ(300u, MetricRecorderImpl::GCCycle_event.memory.after_bytes);
EXPECT_EQ(400u, MetricRecorderImpl::GCCycle_event.memory.freed_bytes);
}
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,884 @@
// 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.
#if defined(CPPGC_YOUNG_GENERATION)
#include <initializer_list>
#include <vector>
#include "include/cppgc/allocation.h"
#include "include/cppgc/explicit-management.h"
#include "include/cppgc/heap-consistency.h"
#include "include/cppgc/internal/caged-heap-local-data.h"
#include "include/cppgc/persistent.h"
#include "src/heap/cppgc/heap-object-header.h"
#include "src/heap/cppgc/heap-visitor.h"
#include "src/heap/cppgc/heap.h"
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace {
bool IsHeapObjectYoung(void* obj) {
return HeapObjectHeader::FromObject(obj).IsYoung();
}
bool IsHeapObjectOld(void* obj) { return !IsHeapObjectYoung(obj); }
class SimpleGCedBase : public GarbageCollected<SimpleGCedBase> {
public:
static size_t destructed_objects;
virtual ~SimpleGCedBase() { ++destructed_objects; }
virtual void Trace(Visitor* v) const { v->Trace(next); }
Member<SimpleGCedBase> next;
};
size_t SimpleGCedBase::destructed_objects;
template <size_t Size>
class SimpleGCed : public SimpleGCedBase {
char array[Size];
};
using Small = SimpleGCed<64>;
using Large = SimpleGCed<kLargeObjectSizeThreshold * 2>;
template <typename Type>
struct OtherType;
template <>
struct OtherType<Small> {
using Type = Large;
};
template <>
struct OtherType<Large> {
using Type = Small;
};
void ExpectPageYoung(BasePage& page) {
EXPECT_TRUE(page.contains_young_objects());
auto& age_table = CagedHeapLocalData::Get().age_table;
EXPECT_EQ(AgeTable::Age::kYoung,
age_table.GetAgeForRange(
CagedHeap::OffsetFromAddress(page.PayloadStart()),
CagedHeap::OffsetFromAddress(page.PayloadEnd())));
}
void ExpectPageMixed(BasePage& page) {
EXPECT_TRUE(page.contains_young_objects());
auto& age_table = CagedHeapLocalData::Get().age_table;
EXPECT_EQ(AgeTable::Age::kMixed,
age_table.GetAgeForRange(
CagedHeap::OffsetFromAddress(page.PayloadStart()),
CagedHeap::OffsetFromAddress(page.PayloadEnd())));
}
void ExpectPageOld(BasePage& page) {
EXPECT_FALSE(page.contains_young_objects());
auto& age_table = CagedHeapLocalData::Get().age_table;
EXPECT_EQ(AgeTable::Age::kOld,
age_table.GetAgeForRange(
CagedHeap::OffsetFromAddress(page.PayloadStart()),
CagedHeap::OffsetFromAddress(page.PayloadEnd())));
}
class RememberedSetExtractor : HeapVisitor<RememberedSetExtractor> {
friend class HeapVisitor<RememberedSetExtractor>;
public:
static std::set<void*> Extract(cppgc::Heap* heap) {
RememberedSetExtractor extractor;
extractor.Traverse(Heap::From(heap)->raw_heap());
return std::move(extractor.slots_);
}
private:
void VisitPage(BasePage& page) {
auto* slot_set = page.slot_set();
if (!slot_set) return;
const uintptr_t page_start = reinterpret_cast<uintptr_t>(&page);
const size_t buckets_size = SlotSet::BucketsForSize(page.AllocatedSize());
slot_set->Iterate(
page_start, 0, buckets_size,
[this](SlotSet::Address slot) {
slots_.insert(reinterpret_cast<void*>(slot));
return heap::base::KEEP_SLOT;
},
SlotSet::EmptyBucketMode::FREE_EMPTY_BUCKETS);
}
bool VisitNormalPage(NormalPage& page) {
VisitPage(page);
return true;
}
bool VisitLargePage(LargePage& page) {
VisitPage(page);
return true;
}
std::set<void*> slots_;
};
} // namespace
class MinorGCTest : public testing::TestWithHeap {
public:
MinorGCTest() : testing::TestWithHeap() {
// Enable young generation flag and run GC. After the first run the heap
// will enable minor GC.
Heap::From(GetHeap())->EnableGenerationalGC();
CollectMajor();
SimpleGCedBase::destructed_objects = 0;
}
~MinorGCTest() override { Heap::From(GetHeap())->Terminate(); }
static size_t DestructedObjects() {
return SimpleGCedBase::destructed_objects;
}
void CollectMinor() {
Heap::From(GetHeap())->CollectGarbage(GCConfig::MinorPreciseAtomicConfig());
}
void CollectMinorWithStack() {
Heap::From(GetHeap())->CollectGarbage(
GCConfig::MinorConservativeAtomicConfig());
}
void CollectMajor() {
Heap::From(GetHeap())->CollectGarbage(GCConfig::PreciseAtomicConfig());
}
void CollectMajorWithStack() {
Heap::From(GetHeap())->CollectGarbage(GCConfig::ConservativeAtomicConfig());
}
const auto& RememberedSourceObjects() const {
return Heap::From(GetHeap())->remembered_set().remembered_source_objects_;
}
const auto& RememberedInConstructionObjects() const {
return Heap::From(GetHeap())
->remembered_set()
.remembered_in_construction_objects_.previous;
}
};
template <typename SmallOrLarge>
class MinorGCTestForType : public MinorGCTest {
public:
using Type = SmallOrLarge;
};
using ObjectTypes = ::testing::Types<Small, Large>;
TYPED_TEST_SUITE(MinorGCTestForType, ObjectTypes);
namespace {
enum class GCType {
kMinor,
kMajor,
};
enum class StackType {
kWithout,
kWith,
};
template <GCType gc_type, StackType stack_type, typename... Args>
void RunGCAndExpectObjectsPromoted(MinorGCTest& test, Args*... args) {
EXPECT_TRUE((IsHeapObjectYoung(args) && ...));
if constexpr (gc_type == GCType::kMajor) {
if constexpr (stack_type == StackType::kWithout) {
test.CollectMajor();
} else {
test.CollectMajorWithStack();
}
} else {
if constexpr (stack_type == StackType::kWithout) {
test.CollectMinor();
} else {
test.CollectMinorWithStack();
}
}
EXPECT_TRUE((IsHeapObjectOld(args) && ...));
}
struct ExpectRememberedSlotsAdded final {
ExpectRememberedSlotsAdded(
const MinorGCTest& test,
std::initializer_list<void*> slots_expected_to_be_remembered)
: test_(test),
slots_expected_to_be_remembered_(slots_expected_to_be_remembered),
initial_slots_(RememberedSetExtractor::Extract(test.GetHeap())) {
// Check that the remembered set doesn't contain specified slots.
EXPECT_FALSE(std::includes(initial_slots_.begin(), initial_slots_.end(),
slots_expected_to_be_remembered_.begin(),
slots_expected_to_be_remembered_.end()));
}
~ExpectRememberedSlotsAdded() {
const auto current_slots = RememberedSetExtractor::Extract(test_.GetHeap());
EXPECT_EQ(initial_slots_.size() + slots_expected_to_be_remembered_.size(),
current_slots.size());
EXPECT_TRUE(std::includes(current_slots.begin(), current_slots.end(),
slots_expected_to_be_remembered_.begin(),
slots_expected_to_be_remembered_.end()));
}
private:
const MinorGCTest& test_;
std::set<void*> slots_expected_to_be_remembered_;
std::set<void*> initial_slots_;
};
struct ExpectRememberedSlotsRemoved final {
ExpectRememberedSlotsRemoved(
const MinorGCTest& test,
std::initializer_list<void*> slots_expected_to_be_removed)
: test_(test),
slots_expected_to_be_removed_(slots_expected_to_be_removed),
initial_slots_(RememberedSetExtractor::Extract(test.GetHeap())) {
DCHECK_GE(initial_slots_.size(), slots_expected_to_be_removed_.size());
// Check that the remembered set does contain specified slots to be removed.
EXPECT_TRUE(std::includes(initial_slots_.begin(), initial_slots_.end(),
slots_expected_to_be_removed_.begin(),
slots_expected_to_be_removed_.end()));
}
~ExpectRememberedSlotsRemoved() {
const auto current_slots = RememberedSetExtractor::Extract(test_.GetHeap());
EXPECT_EQ(initial_slots_.size() - slots_expected_to_be_removed_.size(),
current_slots.size());
EXPECT_FALSE(std::includes(current_slots.begin(), current_slots.end(),
slots_expected_to_be_removed_.begin(),
slots_expected_to_be_removed_.end()));
}
private:
const MinorGCTest& test_;
std::set<void*> slots_expected_to_be_removed_;
std::set<void*> initial_slots_;
};
struct ExpectNoRememberedSlotsAdded final {
explicit ExpectNoRememberedSlotsAdded(const MinorGCTest& test)
: test_(test),
initial_remembered_slots_(
RememberedSetExtractor::Extract(test.GetHeap())) {}
~ExpectNoRememberedSlotsAdded() {
EXPECT_EQ(initial_remembered_slots_,
RememberedSetExtractor::Extract(test_.GetHeap()));
}
private:
const MinorGCTest& test_;
std::set<void*> initial_remembered_slots_;
};
} // namespace
TYPED_TEST(MinorGCTestForType, MinorCollection) {
using Type = typename TestFixture::Type;
MakeGarbageCollected<Type>(this->GetAllocationHandle());
EXPECT_EQ(0u, TestFixture::DestructedObjects());
MinorGCTest::CollectMinor();
EXPECT_EQ(1u, TestFixture::DestructedObjects());
{
subtle::NoGarbageCollectionScope no_gc_scope(*Heap::From(this->GetHeap()));
Type* prev = nullptr;
for (size_t i = 0; i < 64; ++i) {
auto* ptr = MakeGarbageCollected<Type>(this->GetAllocationHandle());
ptr->next = prev;
prev = ptr;
}
}
MinorGCTest::CollectMinor();
EXPECT_EQ(65u, TestFixture::DestructedObjects());
}
TYPED_TEST(MinorGCTestForType, StickyBits) {
using Type = typename TestFixture::Type;
Persistent<Type> p1 = MakeGarbageCollected<Type>(this->GetAllocationHandle());
TestFixture::CollectMinor();
EXPECT_FALSE(HeapObjectHeader::FromObject(p1.Get()).IsYoung());
TestFixture::CollectMajor();
EXPECT_FALSE(HeapObjectHeader::FromObject(p1.Get()).IsYoung());
EXPECT_EQ(0u, TestFixture::DestructedObjects());
}
TYPED_TEST(MinorGCTestForType, OldObjectIsNotVisited) {
using Type = typename TestFixture::Type;
Persistent<Type> p = MakeGarbageCollected<Type>(this->GetAllocationHandle());
TestFixture::CollectMinor();
EXPECT_EQ(0u, TestFixture::DestructedObjects());
EXPECT_FALSE(HeapObjectHeader::FromObject(p.Get()).IsYoung());
// Check that the old deleted object won't be visited during minor GC.
Type* raw = p.Release();
TestFixture::CollectMinor();
EXPECT_EQ(0u, TestFixture::DestructedObjects());
EXPECT_FALSE(HeapObjectHeader::FromObject(raw).IsYoung());
EXPECT_FALSE(HeapObjectHeader::FromObject(raw).IsFree());
// Check that the old deleted object will be revisited in major GC.
TestFixture::CollectMajor();
EXPECT_EQ(1u, TestFixture::DestructedObjects());
}
template <typename Type1, typename Type2>
void InterGenerationalPointerTest(MinorGCTest* test, cppgc::Heap* heap) {
Persistent<Type1> old =
MakeGarbageCollected<Type1>(heap->GetAllocationHandle());
test->CollectMinor();
EXPECT_FALSE(HeapObjectHeader::FromObject(old.Get()).IsYoung());
Type2* young = nullptr;
{
subtle::NoGarbageCollectionScope no_gc_scope(*Heap::From(heap));
// Allocate young objects.
for (size_t i = 0; i < 64; ++i) {
auto* ptr = MakeGarbageCollected<Type2>(heap->GetAllocationHandle());
ptr->next = young;
young = ptr;
EXPECT_TRUE(HeapObjectHeader::FromObject(young).IsYoung());
const uintptr_t offset = CagedHeap::OffsetFromAddress(young);
// Age may be young or unknown.
EXPECT_NE(AgeTable::Age::kOld,
CagedHeapLocalData::Get().age_table.GetAge(offset));
}
}
auto remembered_set_size_before_barrier =
RememberedSetExtractor::Extract(test->GetHeap()).size();
// Issue generational barrier.
old->next = young;
auto remembered_set_size_after_barrier =
RememberedSetExtractor::Extract(test->GetHeap()).size();
EXPECT_EQ(remembered_set_size_before_barrier + 1u,
remembered_set_size_after_barrier);
// Check that the remembered set is visited.
test->CollectMinor();
EXPECT_EQ(0u, MinorGCTest::DestructedObjects());
EXPECT_TRUE(RememberedSetExtractor::Extract(test->GetHeap()).empty());
for (size_t i = 0; i < 64; ++i) {
EXPECT_FALSE(HeapObjectHeader::FromObject(young).IsFree());
EXPECT_FALSE(HeapObjectHeader::FromObject(young).IsYoung());
young = static_cast<Type2*>(young->next.Get());
}
old.Release();
test->CollectMajor();
EXPECT_EQ(65u, MinorGCTest::DestructedObjects());
}
TYPED_TEST(MinorGCTestForType, InterGenerationalPointerForSamePageTypes) {
using Type = typename TestFixture::Type;
InterGenerationalPointerTest<Type, Type>(this, this->GetHeap());
}
TYPED_TEST(MinorGCTestForType, InterGenerationalPointerForDifferentPageTypes) {
using Type = typename TestFixture::Type;
InterGenerationalPointerTest<Type, typename OtherType<Type>::Type>(
this, this->GetHeap());
}
TYPED_TEST(MinorGCTestForType, OmitGenerationalBarrierForOnStackObject) {
using Type = typename TestFixture::Type;
class StackAllocated {
CPPGC_STACK_ALLOCATED();
public:
Type* ptr = nullptr;
} stack_object;
// Try issuing generational barrier for on-stack object.
stack_object.ptr = MakeGarbageCollected<Type>(this->GetAllocationHandle());
subtle::HeapConsistency::WriteBarrierParams params;
EXPECT_EQ(subtle::HeapConsistency::WriteBarrierType::kNone,
subtle::HeapConsistency::GetWriteBarrierType(
reinterpret_cast<void*>(&stack_object.ptr), stack_object.ptr,
params));
}
TYPED_TEST(MinorGCTestForType, OmitGenerationalBarrierForSentinels) {
using Type = typename TestFixture::Type;
Persistent<Type> old =
MakeGarbageCollected<Type>(this->GetAllocationHandle());
TestFixture::CollectMinor();
EXPECT_FALSE(HeapObjectHeader::FromObject(old.Get()).IsYoung());
{
ExpectNoRememberedSlotsAdded _(*this);
// Try issuing generational barrier for nullptr.
old->next = static_cast<Type*>(nullptr);
}
{
ExpectNoRememberedSlotsAdded _(*this);
// Try issuing generational barrier for sentinel.
old->next = kSentinelPointer;
}
}
template <typename From, typename To>
void TestRememberedSetInvalidation(MinorGCTest& test) {
Persistent<From> old = MakeGarbageCollected<From>(test.GetAllocationHandle());
test.CollectMinor();
auto* young = MakeGarbageCollected<To>(test.GetAllocationHandle());
{
ExpectRememberedSlotsAdded _(test, {old->next.GetSlotForTesting()});
// Issue the generational barrier.
old->next = young;
}
{
ExpectRememberedSlotsRemoved _(test, {old->next.GetSlotForTesting()});
// Release the persistent and free the old object.
auto* old_raw = old.Release();
subtle::FreeUnreferencedObject(test.GetHeapHandle(), *old_raw);
}
// Visiting remembered slots must not fail.
test.CollectMinor();
}
TYPED_TEST(MinorGCTestForType, RememberedSetInvalidationOnPromptlyFree) {
using Type1 = typename TestFixture::Type;
using Type2 = typename OtherType<Type1>::Type;
TestRememberedSetInvalidation<Type1, Type1>(*this);
TestRememberedSetInvalidation<Type1, Type2>(*this);
}
TEST_F(MinorGCTest, RememberedSetInvalidationOnShrink) {
using Member = Member<Small>;
static constexpr size_t kTrailingMembers = 64;
static constexpr size_t kBytesToAllocate = kTrailingMembers * sizeof(Member);
static constexpr size_t kFirstMemberToInvalidate = kTrailingMembers / 2;
static constexpr size_t kLastMemberToInvalidate = kTrailingMembers;
// Create an object with additional kBytesToAllocate bytes.
Persistent<Small> old = MakeGarbageCollected<Small>(
this->GetAllocationHandle(), AdditionalBytes(kBytesToAllocate));
auto get_member = [&old](size_t i) -> Member& {
return *reinterpret_cast<Member*>(reinterpret_cast<uint8_t*>(old.Get()) +
sizeof(Small) + i * sizeof(Member));
};
CollectMinor();
auto* young = MakeGarbageCollected<Small>(GetAllocationHandle());
const size_t remembered_set_size_before_barrier =
RememberedSetExtractor::Extract(GetHeap()).size();
// Issue the generational barriers.
for (size_t i = kFirstMemberToInvalidate; i < kLastMemberToInvalidate; ++i) {
// Construct the member.
new (&get_member(i)) Member;
// Issue the barrier.
get_member(i) = young;
}
const auto remembered_set_size_after_barrier =
RememberedSetExtractor::Extract(GetHeap()).size();
// Check that barriers hit (kLastMemberToInvalidate -
// kFirstMemberToInvalidate) times.
EXPECT_EQ(remembered_set_size_before_barrier +
(kLastMemberToInvalidate - kFirstMemberToInvalidate),
remembered_set_size_after_barrier);
// Shrink the buffer for old object.
subtle::Resize(*old, AdditionalBytes(kBytesToAllocate / 2));
const auto remembered_set_after_shrink =
RememberedSetExtractor::Extract(GetHeap()).size();
// Check that the reference was invalidated.
EXPECT_EQ(remembered_set_size_before_barrier, remembered_set_after_shrink);
// Visiting remembered slots must not fail.
CollectMinor();
}
namespace {
template <typename Value>
struct InlinedObject {
CPPGC_DISALLOW_NEW();
struct Inner {
CPPGC_DISALLOW_NEW();
Inner() = default;
explicit Inner(AllocationHandle& handle)
: ref(MakeGarbageCollected<Value>(handle)) {}
void Trace(Visitor* v) const { v->Trace(ref); }
double d = -1.;
Member<Value> ref;
};
InlinedObject() = default;
explicit InlinedObject(AllocationHandle& handle)
: ref(MakeGarbageCollected<Value>(handle)), inner(handle) {}
void Trace(cppgc::Visitor* v) const {
v->Trace(ref);
v->Trace(inner);
}
int a_ = -1;
Member<Value> ref;
Inner inner;
};
template <typename Value>
class GCedWithInlinedArray
: public GarbageCollected<GCedWithInlinedArray<Value>> {
public:
static constexpr size_t kNumObjects = 16;
GCedWithInlinedArray(HeapHandle& heap_handle, AllocationHandle& alloc_handle)
: heap_handle_(heap_handle), alloc_handle_(alloc_handle) {}
using WriteBarrierParams = subtle::HeapConsistency::WriteBarrierParams;
using HeapConsistency = subtle::HeapConsistency;
void SetInPlaceRange(size_t from, size_t to) {
DCHECK_GT(to, from);
DCHECK_GT(kNumObjects, from);
for (; from != to; ++from)
new (&objects[from]) InlinedObject<Value>(alloc_handle_);
GenerationalBarrierForSourceObject(&objects[from]);
}
void Trace(cppgc::Visitor* v) const {
for (const auto& object : objects) v->Trace(object);
}
InlinedObject<Value> objects[kNumObjects];
private:
void GenerationalBarrierForSourceObject(void* object) {
DCHECK(object);
WriteBarrierParams params;
const auto barrier_type = HeapConsistency::GetWriteBarrierType(
object, params, [this]() -> HeapHandle& { return heap_handle_; });
EXPECT_EQ(HeapConsistency::WriteBarrierType::kGenerational, barrier_type);
HeapConsistency::GenerationalBarrierForSourceObject(params, object);
}
HeapHandle& heap_handle_;
AllocationHandle& alloc_handle_;
};
} // namespace
TYPED_TEST(MinorGCTestForType, GenerationalBarrierDeferredTracing) {
using Type = typename TestFixture::Type;
Persistent<GCedWithInlinedArray<Type>> array =
MakeGarbageCollected<GCedWithInlinedArray<Type>>(
this->GetAllocationHandle(), this->GetHeapHandle(),
this->GetAllocationHandle());
this->CollectMinor();
EXPECT_TRUE(IsHeapObjectOld(array.Get()));
const auto& remembered_objects = this->RememberedSourceObjects();
{
ExpectNoRememberedSlotsAdded _(*this);
EXPECT_EQ(0u, remembered_objects.count(
&HeapObjectHeader::FromObject(array->objects)));
array->SetInPlaceRange(2, 4);
EXPECT_EQ(1u, remembered_objects.count(
&HeapObjectHeader::FromObject(array->objects)));
}
RunGCAndExpectObjectsPromoted<GCType::kMinor, StackType::kWithout>(
*this, array->objects[2].ref.Get(), array->objects[2].inner.ref.Get(),
array->objects[3].ref.Get(), array->objects[3].inner.ref.Get());
EXPECT_EQ(0u, remembered_objects.size());
}
namespace {
class GCedWithCustomWeakCallback final
: public GarbageCollected<GCedWithCustomWeakCallback> {
public:
static size_t custom_callback_called;
void CustomWeakCallbackMethod(const LivenessBroker& broker) {
custom_callback_called++;
}
void Trace(cppgc::Visitor* visitor) const {
visitor->RegisterWeakCallbackMethod<
GCedWithCustomWeakCallback,
&GCedWithCustomWeakCallback::CustomWeakCallbackMethod>(this);
}
};
size_t GCedWithCustomWeakCallback::custom_callback_called = 0;
} // namespace
TEST_F(MinorGCTest, ReexecuteCustomCallback) {
// Create an object with additional kBytesToAllocate bytes.
Persistent<GCedWithCustomWeakCallback> old =
MakeGarbageCollected<GCedWithCustomWeakCallback>(GetAllocationHandle());
CollectMinor();
EXPECT_EQ(1u, GCedWithCustomWeakCallback::custom_callback_called);
CollectMinor();
EXPECT_EQ(2u, GCedWithCustomWeakCallback::custom_callback_called);
CollectMinor();
EXPECT_EQ(3u, GCedWithCustomWeakCallback::custom_callback_called);
CollectMajor();
// The callback must be called only once.
EXPECT_EQ(4u, GCedWithCustomWeakCallback::custom_callback_called);
}
TEST_F(MinorGCTest, AgeTableIsReset) {
using Type1 = SimpleGCed<16>;
using Type2 = SimpleGCed<64>;
using Type3 = SimpleGCed<kLargeObjectSizeThreshold * 2>;
Persistent<Type1> p1 = MakeGarbageCollected<Type1>(GetAllocationHandle());
Persistent<Type2> p2 = MakeGarbageCollected<Type2>(GetAllocationHandle());
Persistent<Type3> p3 = MakeGarbageCollected<Type3>(GetAllocationHandle());
auto* page1 = BasePage::FromPayload(p1.Get());
auto* page2 = BasePage::FromPayload(p2.Get());
auto* page3 = BasePage::FromPayload(p3.Get());
ASSERT_FALSE(page1->is_large());
ASSERT_FALSE(page2->is_large());
ASSERT_TRUE(page3->is_large());
ASSERT_NE(page1, page2);
ASSERT_NE(page1, page3);
ASSERT_NE(page2, page3);
// First, expect all the pages to be young.
ExpectPageYoung(*page1);
ExpectPageYoung(*page2);
ExpectPageYoung(*page3);
CollectMinor();
// Expect pages to be promoted after the minor GC.
ExpectPageOld(*page1);
ExpectPageOld(*page2);
ExpectPageOld(*page3);
// Allocate another objects on the normal pages and a new large page.
p1 = MakeGarbageCollected<Type1>(GetAllocationHandle());
p2 = MakeGarbageCollected<Type2>(GetAllocationHandle());
p3 = MakeGarbageCollected<Type3>(GetAllocationHandle());
// Expect now the normal pages to be mixed.
ExpectPageMixed(*page1);
ExpectPageMixed(*page2);
// The large page must remain old.
ExpectPageOld(*page3);
CollectMajor();
// After major GC all the pages must also become old.
ExpectPageOld(*page1);
ExpectPageOld(*page2);
ExpectPageOld(*BasePage::FromPayload(p3.Get()));
}
namespace {
template <GCType type>
struct GCOnConstruction {
explicit GCOnConstruction(MinorGCTest& test, size_t depth) {
if constexpr (type == GCType::kMajor) {
test.CollectMajorWithStack();
} else {
test.CollectMinorWithStack();
}
EXPECT_EQ(depth, test.RememberedInConstructionObjects().size());
}
};
template <GCType type>
struct InConstructionWithYoungRef
: GarbageCollected<InConstructionWithYoungRef<type>> {
using ValueType = SimpleGCed<64>;
explicit InConstructionWithYoungRef(MinorGCTest& test)
: call_gc(test, 1u),
m(MakeGarbageCollected<ValueType>(test.GetAllocationHandle())) {}
void Trace(Visitor* v) const { v->Trace(m); }
GCOnConstruction<type> call_gc;
Member<ValueType> m;
};
} // namespace
TEST_F(MinorGCTest, RevisitInConstructionObjectsMinorMinorWithStack) {
static constexpr auto kFirstGCType = GCType::kMinor;
auto* gced = MakeGarbageCollected<InConstructionWithYoungRef<kFirstGCType>>(
GetAllocationHandle(), *this);
RunGCAndExpectObjectsPromoted<GCType::kMinor, StackType::kWith>(
*this, gced->m.Get());
EXPECT_EQ(0u, RememberedInConstructionObjects().size());
}
TEST_F(MinorGCTest, RevisitInConstructionObjectsMinorMinorWithoutStack) {
static constexpr auto kFirstGCType = GCType::kMinor;
Persistent<InConstructionWithYoungRef<kFirstGCType>> gced =
MakeGarbageCollected<InConstructionWithYoungRef<kFirstGCType>>(
GetAllocationHandle(), *this);
RunGCAndExpectObjectsPromoted<GCType::kMinor, StackType::kWithout>(
*this, gced->m.Get());
EXPECT_EQ(0u, RememberedInConstructionObjects().size());
}
TEST_F(MinorGCTest, RevisitInConstructionObjectsMajorMinorWithStack) {
static constexpr auto kFirstGCType = GCType::kMajor;
auto* gced = MakeGarbageCollected<InConstructionWithYoungRef<kFirstGCType>>(
GetAllocationHandle(), *this);
RunGCAndExpectObjectsPromoted<GCType::kMinor, StackType::kWith>(
*this, gced->m.Get());
EXPECT_EQ(0u, RememberedInConstructionObjects().size());
}
TEST_F(MinorGCTest, RevisitInConstructionObjectsMajorMinorWithoutStack) {
static constexpr auto kFirstGCType = GCType::kMajor;
Persistent<InConstructionWithYoungRef<kFirstGCType>> gced =
MakeGarbageCollected<InConstructionWithYoungRef<kFirstGCType>>(
GetAllocationHandle(), *this);
RunGCAndExpectObjectsPromoted<GCType::kMinor, StackType::kWithout>(
*this, gced->m.Get());
EXPECT_EQ(0u, RememberedInConstructionObjects().size());
}
TEST_F(MinorGCTest, PreviousInConstructionObjectsAreDroppedAfterFullGC) {
MakeGarbageCollected<InConstructionWithYoungRef<GCType::kMinor>>(
GetAllocationHandle(), *this);
EXPECT_EQ(1u, RememberedInConstructionObjects().size());
CollectMajor();
EXPECT_EQ(0u, RememberedInConstructionObjects().size());
}
namespace {
template <GCType type>
struct NestedInConstructionWithYoungRef
: GarbageCollected<NestedInConstructionWithYoungRef<type>> {
using ValueType = SimpleGCed<64>;
NestedInConstructionWithYoungRef(MinorGCTest& test, size_t depth)
: NestedInConstructionWithYoungRef(test, 1, depth) {}
NestedInConstructionWithYoungRef(MinorGCTest& test, size_t current_depth,
size_t max_depth)
: current_depth(current_depth),
max_depth(max_depth),
next(current_depth != max_depth
? MakeGarbageCollected<NestedInConstructionWithYoungRef<type>>(
test.GetAllocationHandle(), test, current_depth + 1,
max_depth)
: nullptr),
call_gc(test, current_depth),
m(MakeGarbageCollected<ValueType>(test.GetAllocationHandle())) {}
void Trace(Visitor* v) const {
v->Trace(next);
v->Trace(m);
}
size_t current_depth = 0;
size_t max_depth = 0;
Member<NestedInConstructionWithYoungRef<type>> next;
GCOnConstruction<type> call_gc;
Member<ValueType> m;
};
} // namespace
TEST_F(MinorGCTest, RevisitNestedInConstructionObjects) {
static constexpr auto kFirstGCType = GCType::kMinor;
Persistent<NestedInConstructionWithYoungRef<kFirstGCType>> gced =
MakeGarbageCollected<NestedInConstructionWithYoungRef<kFirstGCType>>(
GetAllocationHandle(), *this, 10);
CollectMinor();
for (auto* p = gced.Get(); p; p = p->next.Get()) {
EXPECT_TRUE(IsHeapObjectOld(p));
EXPECT_TRUE(IsHeapObjectOld(p->m));
}
EXPECT_EQ(0u, RememberedInConstructionObjects().size());
}
} // namespace internal
} // namespace cppgc
#endif

View File

@ -0,0 +1,164 @@
// 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<ClassWithName>,
public NameProvider {
public:
explicit ClassWithName(const char* name) : name_(name) {}
void Trace(Visitor*) const {}
const char* GetHumanReadableName() const final { return name_; }
private:
const char* name_;
};
} // namespace
class NameTraitTest : public testing::TestWithHeap {};
TEST_F(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_FALSE(NameProvider::SupportsCppClassNamesAsObjectNames());
#endif
}
TEST_F(NameTraitTest, DefaultName) {
EXPECT_STREQ(
NameProvider::SupportsCppClassNamesAsObjectNames()
? "cppgc::internal::(anonymous namespace)::NoName"
: "InternalNode",
NameTrait<NoName>::GetName(
nullptr, HeapObjectNameForUnnamedObject::kUseClassNameIfSupported)
.value);
EXPECT_STREQ(
NameProvider::SupportsCppClassNamesAsObjectNames()
? "cppgc::internal::(anonymous namespace)::OtherNoName"
: "InternalNode",
NameTrait<OtherNoName>::GetName(
nullptr, HeapObjectNameForUnnamedObject::kUseClassNameIfSupported)
.value);
// The following ignores `NameProvider::SupportsCppClassNamesAsObjectNames()`
// and just always returns the hidden name, independent of the build support.
EXPECT_STREQ("InternalNode",
NameTrait<NoName>::GetName(
nullptr, HeapObjectNameForUnnamedObject::kUseHiddenName)
.value);
EXPECT_STREQ("InternalNode",
NameTrait<OtherNoName>::GetName(
nullptr, HeapObjectNameForUnnamedObject::kUseHiddenName)
.value);
}
TEST_F(NameTraitTest, CustomName) {
ClassWithName* with_name =
MakeGarbageCollected<ClassWithName>(GetAllocationHandle(), "CustomName");
EXPECT_STREQ(
"CustomName",
NameTrait<ClassWithName>::GetName(
with_name, HeapObjectNameForUnnamedObject::kUseClassNameIfSupported)
.value);
EXPECT_STREQ("CustomName",
NameTrait<ClassWithName>::GetName(
with_name, HeapObjectNameForUnnamedObject::kUseHiddenName)
.value);
}
namespace {
class TraitTester : public NameTraitBase {
public:
// Expose type signature parser to allow testing various inputs.
using NameTraitBase::GetNameFromTypeSignature;
};
} // namespace
TEST_F(NameTraitTest, NoTypeAvailable) {
HeapObjectName name = TraitTester::GetNameFromTypeSignature(nullptr);
EXPECT_STREQ(NameProvider::kNoNameDeducible, name.value);
EXPECT_FALSE(name.name_was_hidden);
}
TEST_F(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::FromObject(no_name).GetName();
EXPECT_STREQ(NameProvider::kHiddenName, no_name_tuple.value);
EXPECT_TRUE(no_name_tuple.name_was_hidden);
ClassNameAsHeapObjectNameScope class_names_scope(*Heap::From(GetHeap()));
no_name_tuple = HeapObjectHeader::FromObject(no_name).GetName();
if (NameProvider::SupportsCppClassNamesAsObjectNames()) {
EXPECT_STREQ("cppgc::internal::(anonymous namespace)::NoName",
no_name_tuple.value);
EXPECT_FALSE(no_name_tuple.name_was_hidden);
} else {
EXPECT_STREQ(NameProvider::kHiddenName, 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::FromObject(other_no_name).GetName();
if (NameProvider::SupportsCppClassNamesAsObjectNames()) {
EXPECT_STREQ("cppgc::internal::(anonymous namespace)::OtherNoName",
other_no_name_tuple.value);
EXPECT_FALSE(other_no_name_tuple.name_was_hidden);
} else {
EXPECT_STREQ(NameProvider::kHiddenName, 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::FromObject(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

View File

@ -0,0 +1,51 @@
// 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/object-size-trait.h"
#include "include/cppgc/allocation.h"
#include "include/cppgc/garbage-collected.h"
#include "src/heap/cppgc/heap.h"
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace {
class ObjectSizeTraitTest : public testing::TestWithHeap {};
class GCed : public GarbageCollected<GCed> {
public:
void Trace(Visitor*) const {}
};
class NotGCed {};
class Mixin : public GarbageCollectedMixin {};
class UnmanagedMixinWithDouble {
protected:
virtual void ForceVTable() {}
};
class GCedWithMixin : public GarbageCollected<GCedWithMixin>,
public UnmanagedMixinWithDouble,
public Mixin {};
} // namespace
TEST_F(ObjectSizeTraitTest, GarbageCollected) {
auto* obj = cppgc::MakeGarbageCollected<GCed>(GetAllocationHandle());
EXPECT_GE(subtle::ObjectSizeTrait<GCed>::GetSize(*obj), sizeof(GCed));
}
TEST_F(ObjectSizeTraitTest, GarbageCollectedMixin) {
auto* obj = cppgc::MakeGarbageCollected<GCedWithMixin>(GetAllocationHandle());
Mixin& mixin = static_cast<Mixin&>(*obj);
EXPECT_NE(static_cast<void*>(&mixin), obj);
EXPECT_GE(subtle::ObjectSizeTrait<Mixin>::GetSize(mixin),
sizeof(GCedWithMixin));
}
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,190 @@
// 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/object-start-bitmap.h"
#include "include/cppgc/allocation.h"
#include "src/base/macros.h"
#include "src/base/page-allocator.h"
#include "src/heap/cppgc/globals.h"
#include "src/heap/cppgc/heap-object-header.h"
#include "src/heap/cppgc/page-memory.h"
#include "src/heap/cppgc/raw-heap.h"
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace {
class PageWithBitmap final {
public:
PageWithBitmap()
: base_(allocator_.AllocatePages(
nullptr, kPageSize, kPageSize,
v8::base::PageAllocator::Permission::kReadWrite)),
bitmap_(new(base_) ObjectStartBitmap) {}
PageWithBitmap(const PageWithBitmap&) = delete;
PageWithBitmap& operator=(const PageWithBitmap&) = delete;
~PageWithBitmap() { allocator_.FreePages(base_, kPageSize); }
ObjectStartBitmap& bitmap() const { return *bitmap_; }
void* base() const { return base_; }
size_t size() const { return kPageSize; }
v8::base::PageAllocator allocator_;
void* base_;
ObjectStartBitmap* bitmap_;
};
class ObjectStartBitmapTest : public ::testing::Test {
protected:
void AllocateObject(size_t object_position) {
bitmap().SetBit(ObjectAddress(object_position));
}
void FreeObject(size_t object_position) {
bitmap().ClearBit(ObjectAddress(object_position));
}
bool CheckObjectAllocated(size_t object_position) {
return bitmap().CheckBit(ObjectAddress(object_position));
}
Address ObjectAddress(size_t pos) const {
return reinterpret_cast<Address>(reinterpret_cast<uintptr_t>(page.base()) +
pos * ObjectStartBitmap::Granularity());
}
HeapObjectHeader* ObjectHeader(size_t pos) const {
return reinterpret_cast<HeapObjectHeader*>(ObjectAddress(pos));
}
ObjectStartBitmap& bitmap() const { return page.bitmap(); }
bool IsEmpty() const {
size_t count = 0;
bitmap().Iterate([&count](Address) { count++; });
return count == 0;
}
private:
PageWithBitmap page;
};
} // namespace
TEST_F(ObjectStartBitmapTest, MoreThanZeroEntriesPossible) {
const size_t max_entries = ObjectStartBitmap::MaxEntries();
EXPECT_LT(0u, max_entries);
}
TEST_F(ObjectStartBitmapTest, InitialEmpty) { EXPECT_TRUE(IsEmpty()); }
TEST_F(ObjectStartBitmapTest, SetBitImpliesNonEmpty) {
AllocateObject(0);
EXPECT_FALSE(IsEmpty());
}
TEST_F(ObjectStartBitmapTest, SetBitCheckBit) {
constexpr size_t object_num = 7;
AllocateObject(object_num);
EXPECT_TRUE(CheckObjectAllocated(object_num));
}
TEST_F(ObjectStartBitmapTest, SetBitClearbitCheckBit) {
constexpr size_t object_num = 77;
AllocateObject(object_num);
FreeObject(object_num);
EXPECT_FALSE(CheckObjectAllocated(object_num));
}
TEST_F(ObjectStartBitmapTest, SetBitClearBitImpliesEmpty) {
constexpr size_t object_num = 123;
AllocateObject(object_num);
FreeObject(object_num);
EXPECT_TRUE(IsEmpty());
}
TEST_F(ObjectStartBitmapTest, AdjacentObjectsAtBegin) {
AllocateObject(0);
AllocateObject(1);
EXPECT_FALSE(CheckObjectAllocated(3));
size_t count = 0;
bitmap().Iterate([&count, this](Address current) {
if (count == 0) {
EXPECT_EQ(ObjectAddress(0), current);
} else if (count == 1) {
EXPECT_EQ(ObjectAddress(1), current);
}
count++;
});
EXPECT_EQ(2u, count);
}
TEST_F(ObjectStartBitmapTest, AdjacentObjectsAtEnd) {
static constexpr size_t last_entry_index =
ObjectStartBitmap::MaxEntries() - 1;
AllocateObject(last_entry_index);
AllocateObject(last_entry_index - 1);
EXPECT_FALSE(CheckObjectAllocated(last_entry_index - 2));
size_t count = 0;
bitmap().Iterate([&count, this](Address current) {
if (count == 0) {
EXPECT_EQ(ObjectAddress(last_entry_index - 1), current);
} else if (count == 1) {
EXPECT_EQ(ObjectAddress(last_entry_index), current);
}
count++;
});
EXPECT_EQ(2u, count);
}
TEST_F(ObjectStartBitmapTest, FindHeaderExact) {
constexpr size_t object_num = 654;
AllocateObject(object_num);
EXPECT_EQ(ObjectHeader(object_num),
bitmap().FindHeader(ObjectAddress(object_num)));
}
TEST_F(ObjectStartBitmapTest, FindHeaderApproximate) {
static const size_t kInternalDelta = 37;
constexpr size_t object_num = 654;
AllocateObject(object_num);
EXPECT_EQ(ObjectHeader(object_num),
bitmap().FindHeader(ObjectAddress(object_num) + kInternalDelta));
}
TEST_F(ObjectStartBitmapTest, FindHeaderIteratingWholeBitmap) {
AllocateObject(0);
Address hint_index = ObjectAddress(ObjectStartBitmap::MaxEntries() - 1);
EXPECT_EQ(ObjectHeader(0), bitmap().FindHeader(hint_index));
}
TEST_F(ObjectStartBitmapTest, FindHeaderNextCell) {
// This white box test makes use of the fact that cells are of type uint8_t.
const size_t kCellSize = sizeof(uint8_t);
AllocateObject(0);
AllocateObject(kCellSize - 1);
Address hint = ObjectAddress(kCellSize);
EXPECT_EQ(ObjectHeader(kCellSize - 1), bitmap().FindHeader(hint));
}
TEST_F(ObjectStartBitmapTest, FindHeaderSameCell) {
// This white box test makes use of the fact that cells are of type uint8_t.
const size_t kCellSize = sizeof(uint8_t);
AllocateObject(0);
AllocateObject(kCellSize - 1);
Address hint = ObjectAddress(kCellSize);
EXPECT_EQ(ObjectHeader(kCellSize - 1), bitmap().FindHeader(hint));
EXPECT_EQ(ObjectHeader(kCellSize - 1),
bitmap().FindHeader(ObjectAddress(kCellSize - 1)));
}
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,298 @@
// 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/page-memory.h"
#include <algorithm>
#include "src/base/page-allocator.h"
#include "src/heap/cppgc/platform.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
TEST(MemoryRegionTest, Construct) {
constexpr size_t kSize = 17;
uint8_t dummy[kSize];
const MemoryRegion region(dummy, kSize);
EXPECT_EQ(dummy, region.base());
EXPECT_EQ(kSize, region.size());
EXPECT_EQ(dummy + kSize, region.end());
}
namespace {
Address AtOffset(uint8_t* base, intptr_t offset) {
return reinterpret_cast<Address>(reinterpret_cast<intptr_t>(base) + offset);
}
} // namespace
TEST(MemoryRegionTest, ContainsAddress) {
constexpr size_t kSize = 7;
uint8_t dummy[kSize];
const MemoryRegion region(dummy, kSize);
EXPECT_FALSE(region.Contains(AtOffset(dummy, -1)));
EXPECT_TRUE(region.Contains(dummy));
EXPECT_TRUE(region.Contains(dummy + kSize - 1));
EXPECT_FALSE(region.Contains(AtOffset(dummy, kSize)));
}
TEST(MemoryRegionTest, ContainsMemoryRegion) {
constexpr size_t kSize = 7;
uint8_t dummy[kSize];
const MemoryRegion region(dummy, kSize);
const MemoryRegion contained_region1(dummy, kSize - 1);
EXPECT_TRUE(region.Contains(contained_region1));
const MemoryRegion contained_region2(dummy + 1, kSize - 1);
EXPECT_TRUE(region.Contains(contained_region2));
const MemoryRegion not_contained_region1(AtOffset(dummy, -1), kSize);
EXPECT_FALSE(region.Contains(not_contained_region1));
const MemoryRegion not_contained_region2(AtOffset(dummy, kSize), 1);
EXPECT_FALSE(region.Contains(not_contained_region2));
}
namespace {
V8_NOINLINE uint8_t access(volatile const uint8_t& u) { return u; }
} // namespace
TEST(PageBackendDeathTest, ReservationIsFreed) {
// Full sequence as part of the death test macro as otherwise, the macro
// may expand to statements that re-purpose the previously freed memory
// and thus not crash.
EXPECT_DEATH_IF_SUPPORTED(
v8::base::PageAllocator allocator; Address base; {
PageBackend backend(allocator, allocator);
base = backend.TryAllocateLargePageMemory(1024);
} access(*base);
, "");
}
TEST(PageBackendTreeTest, AddNormalLookupRemove) {
v8::base::PageAllocator allocator;
PageBackend backend(allocator, allocator);
auto* writable_base = backend.TryAllocateNormalPageMemory();
auto* reserved_base = writable_base;
auto& tree = backend.get_page_memory_region_tree_for_testing();
ASSERT_EQ(reserved_base, tree.Lookup(reserved_base)->region().base());
ASSERT_EQ(reserved_base,
tree.Lookup(reserved_base + kPageSize - 1)->region().base());
ASSERT_EQ(nullptr, tree.Lookup(reserved_base - 1));
ASSERT_EQ(nullptr, tree.Lookup(reserved_base + kPageSize));
backend.FreeNormalPageMemory(writable_base);
ASSERT_EQ(nullptr, tree.Lookup(reserved_base));
ASSERT_EQ(nullptr, tree.Lookup(reserved_base + kPageSize - 1));
}
TEST(PageBackendTreeTest, AddLargeLookupRemove) {
v8::base::PageAllocator allocator;
constexpr size_t kLargeSize = 5012;
const size_t allocated_page_size =
RoundUp(kLargeSize, allocator.AllocatePageSize());
PageBackend backend(allocator, allocator);
auto* writable_base = backend.TryAllocateLargePageMemory(kLargeSize);
auto* reserved_base = writable_base;
auto& tree = backend.get_page_memory_region_tree_for_testing();
ASSERT_EQ(reserved_base, tree.Lookup(reserved_base)->region().base());
ASSERT_EQ(
reserved_base,
tree.Lookup(reserved_base + allocated_page_size - 1)->region().base());
ASSERT_EQ(nullptr, tree.Lookup(reserved_base - 1));
ASSERT_EQ(nullptr, tree.Lookup(reserved_base + allocated_page_size));
backend.FreeLargePageMemory(writable_base);
ASSERT_EQ(nullptr, tree.Lookup(reserved_base));
ASSERT_EQ(nullptr, tree.Lookup(reserved_base + allocated_page_size - 1));
}
TEST(PageBackendTreeTest, AddLookupRemoveMultiple) {
v8::base::PageAllocator allocator;
constexpr size_t kLargeSize = 3127;
const size_t allocated_page_size =
RoundUp(kLargeSize, allocator.AllocatePageSize());
PageBackend backend(allocator, allocator);
auto& tree = backend.get_page_memory_region_tree_for_testing();
auto* writable_normal_base = backend.TryAllocateNormalPageMemory();
auto* reserved_normal_base = writable_normal_base;
auto* writable_large_base = backend.TryAllocateLargePageMemory(kLargeSize);
auto* reserved_large_base = writable_large_base;
ASSERT_EQ(reserved_normal_base,
tree.Lookup(reserved_normal_base)->region().base());
ASSERT_EQ(reserved_normal_base,
tree.Lookup(reserved_normal_base + kPageSize - 1)->region().base());
ASSERT_EQ(reserved_large_base,
tree.Lookup(reserved_large_base)->region().base());
ASSERT_EQ(reserved_large_base,
tree.Lookup(reserved_large_base + allocated_page_size - 1)
->region()
.base());
backend.FreeNormalPageMemory(writable_normal_base);
ASSERT_EQ(reserved_large_base,
tree.Lookup(reserved_large_base)->region().base());
ASSERT_EQ(reserved_large_base,
tree.Lookup(reserved_large_base + allocated_page_size - 1)
->region()
.base());
backend.FreeLargePageMemory(writable_large_base);
ASSERT_EQ(nullptr, tree.Lookup(reserved_large_base));
ASSERT_EQ(nullptr,
tree.Lookup(reserved_large_base + allocated_page_size - 1));
}
TEST(PageBackendPoolTest, ConstructorEmpty) {
v8::base::PageAllocator allocator;
PageBackend backend(allocator, allocator);
auto& pool = backend.page_pool();
EXPECT_EQ(nullptr, pool.Take());
}
TEST(PageBackendPoolTest, AddTake) {
v8::base::PageAllocator allocator;
PageBackend backend(allocator, allocator);
auto& pool = backend.page_pool();
auto& raw_pool = pool.get_raw_pool_for_testing();
EXPECT_TRUE(raw_pool.empty());
auto* writable_base1 = backend.TryAllocateNormalPageMemory();
EXPECT_TRUE(raw_pool.empty());
backend.FreeNormalPageMemory(writable_base1);
EXPECT_FALSE(raw_pool.empty());
EXPECT_TRUE(raw_pool[0].region);
EXPECT_EQ(raw_pool[0].region->region().base(), writable_base1);
auto* writable_base2 = backend.TryAllocateNormalPageMemory();
EXPECT_TRUE(raw_pool.empty());
EXPECT_EQ(writable_base1, writable_base2);
}
namespace {
void AddTakeWithDiscardInBetween(bool decommit_pooled_pages) {
v8::base::PageAllocator allocator;
PageBackend backend(allocator, allocator);
auto& pool = backend.page_pool();
pool.SetDecommitPooledPages(decommit_pooled_pages);
auto& raw_pool = pool.get_raw_pool_for_testing();
EXPECT_TRUE(raw_pool.empty());
auto* writable_base1 = backend.TryAllocateNormalPageMemory();
EXPECT_TRUE(raw_pool.empty());
EXPECT_EQ(0u, pool.PooledMemory());
backend.FreeNormalPageMemory(writable_base1);
EXPECT_FALSE(raw_pool.empty());
EXPECT_TRUE(raw_pool[0].region);
EXPECT_EQ(raw_pool[0].region->region().base(), writable_base1);
size_t size = raw_pool[0].region->region().size();
EXPECT_EQ(size, pool.PooledMemory());
backend.ReleasePooledPages();
// Not couting discarded memory.
EXPECT_EQ(0u, pool.PooledMemory());
auto* writable_base2 = backend.TryAllocateNormalPageMemory();
EXPECT_TRUE(raw_pool.empty());
EXPECT_EQ(0u, pool.PooledMemory());
EXPECT_EQ(writable_base1, writable_base2);
// Should not die: memory is writable.
memset(writable_base2, 12, size);
}
} // namespace
TEST(PageBackendPoolTest, AddTakeWithDiscardInBetween) {
AddTakeWithDiscardInBetween(false);
}
TEST(PageBackendPoolTest, AddTakeWithDiscardInBetweenWithDecommit) {
AddTakeWithDiscardInBetween(true);
}
TEST(PageBackendPoolTest, PoolMemoryAccounting) {
v8::base::PageAllocator allocator;
PageBackend backend(allocator, allocator);
auto& pool = backend.page_pool();
auto* writable_base1 = backend.TryAllocateNormalPageMemory();
auto* writable_base2 = backend.TryAllocateNormalPageMemory();
backend.FreeNormalPageMemory(writable_base1);
backend.FreeNormalPageMemory(writable_base2);
size_t normal_page_size =
pool.get_raw_pool_for_testing()[0].region->region().size();
EXPECT_EQ(2 * normal_page_size, pool.PooledMemory());
backend.ReleasePooledPages();
EXPECT_EQ(0u, pool.PooledMemory());
auto* writable_base3 = backend.TryAllocateNormalPageMemory();
backend.FreeNormalPageMemory(writable_base3);
// One discarded, one not discarded.
EXPECT_EQ(normal_page_size, pool.PooledMemory());
backend.ReleasePooledPages();
EXPECT_EQ(0u, pool.PooledMemory());
}
TEST(PageBackendTest, AllocateNormalUsesPool) {
v8::base::PageAllocator allocator;
PageBackend backend(allocator, allocator);
Address writeable_base1 = backend.TryAllocateNormalPageMemory();
EXPECT_NE(nullptr, writeable_base1);
backend.FreeNormalPageMemory(writeable_base1);
Address writeable_base2 = backend.TryAllocateNormalPageMemory();
EXPECT_NE(nullptr, writeable_base2);
EXPECT_EQ(writeable_base1, writeable_base2);
}
TEST(PageBackendTest, AllocateLarge) {
v8::base::PageAllocator allocator;
PageBackend backend(allocator, allocator);
Address writeable_base1 = backend.TryAllocateLargePageMemory(13731);
EXPECT_NE(nullptr, writeable_base1);
Address writeable_base2 = backend.TryAllocateLargePageMemory(9478);
EXPECT_NE(nullptr, writeable_base2);
EXPECT_NE(writeable_base1, writeable_base2);
backend.FreeLargePageMemory(writeable_base1);
backend.FreeLargePageMemory(writeable_base2);
}
TEST(PageBackendTest, LookupNormal) {
v8::base::PageAllocator allocator;
PageBackend backend(allocator, allocator);
Address writeable_base = backend.TryAllocateNormalPageMemory();
EXPECT_EQ(nullptr, backend.Lookup(writeable_base - 1));
EXPECT_EQ(writeable_base, backend.Lookup(writeable_base));
EXPECT_EQ(writeable_base, backend.Lookup(writeable_base + kPageSize - 1));
EXPECT_EQ(nullptr, backend.Lookup(writeable_base + kPageSize));
}
TEST(PageBackendTest, LookupLarge) {
v8::base::PageAllocator allocator;
PageBackend backend(allocator, allocator);
constexpr size_t kSize = 7934;
Address writeable_base = backend.TryAllocateLargePageMemory(kSize);
EXPECT_EQ(nullptr, backend.Lookup(writeable_base - 1));
EXPECT_EQ(writeable_base, backend.Lookup(writeable_base));
EXPECT_EQ(writeable_base, backend.Lookup(writeable_base + kSize - 1));
}
TEST(PageBackendDeathTest, DestructingBackendDestroysPageMemory) {
v8::base::PageAllocator allocator;
Address base;
{
PageBackend backend(allocator, allocator);
base = backend.TryAllocateNormalPageMemory();
}
EXPECT_DEATH_IF_SUPPORTED(access(base[0]), "");
}
} // namespace internal
} // namespace cppgc

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,46 @@
// Copyright 2021 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/platform.h"
#include "src/base/logging.h"
#include "src/base/page-allocator.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
TEST(FatalOutOfMemoryHandlerDeathTest, DefaultHandlerCrashes) {
FatalOutOfMemoryHandler handler;
EXPECT_DEATH_IF_SUPPORTED(handler(), "");
}
namespace {
constexpr uintptr_t kHeapNeedle = 0x14;
[[noreturn]] void CustomHandler(const std::string&, const SourceLocation&,
HeapBase* heap) {
if (heap == reinterpret_cast<HeapBase*>(kHeapNeedle)) {
GRACEFUL_FATAL("cust0m h4ndl3r with matching heap");
}
GRACEFUL_FATAL("cust0m h4ndl3r");
}
} // namespace
TEST(FatalOutOfMemoryHandlerDeathTest, CustomHandlerCrashes) {
FatalOutOfMemoryHandler handler;
handler.SetCustomHandler(&CustomHandler);
EXPECT_DEATH_IF_SUPPORTED(handler(), "cust0m h4ndl3r");
}
TEST(FatalOutOfMemoryHandlerDeathTest, CustomHandlerWithHeapState) {
FatalOutOfMemoryHandler handler(reinterpret_cast<HeapBase*>(kHeapNeedle));
handler.SetCustomHandler(&CustomHandler);
EXPECT_DEATH_IF_SUPPORTED(handler(), "cust0m h4ndl3r with matching heap");
}
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,377 @@
// 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/prefinalizer.h"
#include "include/cppgc/allocation.h"
#include "include/cppgc/garbage-collected.h"
#include "include/cppgc/persistent.h"
#include "src/heap/cppgc/heap-object-header.h"
#include "src/heap/cppgc/heap.h"
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace {
class PrefinalizerTest : public testing::TestWithHeap {};
class GCed : public GarbageCollected<GCed> {
CPPGC_USING_PRE_FINALIZER(GCed, PreFinalizer);
public:
void Trace(Visitor*) const {}
void PreFinalizer() { ++prefinalizer_callcount; }
static size_t prefinalizer_callcount;
};
size_t GCed::prefinalizer_callcount = 0;
} // namespace
TEST_F(PrefinalizerTest, PrefinalizerCalledOnDeadObject) {
GCed::prefinalizer_callcount = 0;
auto* object = MakeGarbageCollected<GCed>(GetAllocationHandle());
USE(object);
EXPECT_EQ(0u, GCed::prefinalizer_callcount);
PreciseGC();
EXPECT_EQ(1u, GCed::prefinalizer_callcount);
PreciseGC();
EXPECT_EQ(1u, GCed::prefinalizer_callcount);
}
TEST_F(PrefinalizerTest, PrefinalizerNotCalledOnLiveObject) {
GCed::prefinalizer_callcount = 0;
{
Persistent<GCed> object = MakeGarbageCollected<GCed>(GetAllocationHandle());
EXPECT_EQ(0u, GCed::prefinalizer_callcount);
PreciseGC();
EXPECT_EQ(0u, GCed::prefinalizer_callcount);
}
PreciseGC();
EXPECT_EQ(1u, GCed::prefinalizer_callcount);
}
namespace {
class Mixin : public GarbageCollectedMixin {
CPPGC_USING_PRE_FINALIZER(Mixin, PreFinalizer);
public:
void PreFinalizer() { ++prefinalizer_callcount; }
static size_t prefinalizer_callcount;
};
size_t Mixin::prefinalizer_callcount = 0;
class GCedWithMixin : public GarbageCollected<GCedWithMixin>, public Mixin {};
} // namespace
TEST_F(PrefinalizerTest, PrefinalizerCalledOnDeadMixinObject) {
Mixin::prefinalizer_callcount = 0;
auto* object = MakeGarbageCollected<GCedWithMixin>(GetAllocationHandle());
USE(object);
EXPECT_EQ(0u, Mixin::prefinalizer_callcount);
PreciseGC();
EXPECT_EQ(1u, Mixin::prefinalizer_callcount);
PreciseGC();
EXPECT_EQ(1u, Mixin::prefinalizer_callcount);
}
TEST_F(PrefinalizerTest, PrefinalizerNotCalledOnLiveMixinObject) {
Mixin::prefinalizer_callcount = 0;
{
Persistent<GCedWithMixin> object =
MakeGarbageCollected<GCedWithMixin>(GetAllocationHandle());
EXPECT_EQ(0u, Mixin::prefinalizer_callcount);
PreciseGC();
EXPECT_EQ(0u, Mixin::prefinalizer_callcount);
}
PreciseGC();
EXPECT_EQ(1u, Mixin::prefinalizer_callcount);
}
namespace {
class BaseMixin : public GarbageCollectedMixin {
CPPGC_USING_PRE_FINALIZER(BaseMixin, PreFinalizer);
public:
void PreFinalizer();
static size_t prefinalizer_callcount;
};
size_t BaseMixin::prefinalizer_callcount = 0;
class InheritingMixin : public BaseMixin {
CPPGC_USING_PRE_FINALIZER(InheritingMixin, PreFinalizer);
public:
void PreFinalizer();
static size_t prefinalizer_callcount;
};
size_t InheritingMixin::prefinalizer_callcount = 0;
class GCedWithMixins : public GarbageCollected<GCedWithMixins>,
public InheritingMixin {
CPPGC_USING_PRE_FINALIZER(GCedWithMixins, PreFinalizer);
public:
void PreFinalizer();
static size_t prefinalizer_callcount;
};
size_t GCedWithMixins::prefinalizer_callcount = 0;
void BaseMixin::PreFinalizer() {
EXPECT_EQ(1u, GCedWithMixins::prefinalizer_callcount);
EXPECT_EQ(1u, InheritingMixin::prefinalizer_callcount);
EXPECT_EQ(0u, BaseMixin::prefinalizer_callcount);
++BaseMixin::prefinalizer_callcount;
}
void InheritingMixin::PreFinalizer() {
EXPECT_EQ(1u, GCedWithMixins::prefinalizer_callcount);
EXPECT_EQ(0u, InheritingMixin::prefinalizer_callcount);
EXPECT_EQ(0u, BaseMixin::prefinalizer_callcount);
InheritingMixin::prefinalizer_callcount = true;
}
void GCedWithMixins::PreFinalizer() {
EXPECT_EQ(0u, GCedWithMixins::prefinalizer_callcount);
EXPECT_EQ(0u, InheritingMixin::prefinalizer_callcount);
EXPECT_EQ(0u, BaseMixin::prefinalizer_callcount);
GCedWithMixins::prefinalizer_callcount = true;
}
} // namespace
TEST_F(PrefinalizerTest, PrefinalizerInvocationPreservesOrder) {
BaseMixin::prefinalizer_callcount = 0;
InheritingMixin::prefinalizer_callcount = 0;
GCedWithMixins::prefinalizer_callcount = 0;
auto* object = MakeGarbageCollected<GCedWithMixins>(GetAllocationHandle());
USE(object);
EXPECT_EQ(0u, GCedWithMixins::prefinalizer_callcount);
EXPECT_EQ(0u, InheritingMixin::prefinalizer_callcount);
EXPECT_EQ(0u, BaseMixin::prefinalizer_callcount);
PreciseGC();
EXPECT_EQ(1u, GCedWithMixins::prefinalizer_callcount);
EXPECT_EQ(1u, InheritingMixin::prefinalizer_callcount);
EXPECT_EQ(1u, BaseMixin::prefinalizer_callcount);
PreciseGC();
EXPECT_EQ(1u, GCedWithMixins::prefinalizer_callcount);
EXPECT_EQ(1u, InheritingMixin::prefinalizer_callcount);
EXPECT_EQ(1u, BaseMixin::prefinalizer_callcount);
}
namespace {
class LinkedNode final : public GarbageCollected<LinkedNode> {
public:
explicit LinkedNode(LinkedNode* next) : next_(next) {}
void Trace(Visitor* visitor) const { visitor->Trace(next_); }
LinkedNode* next() const { return next_; }
void RemoveNext() {
CHECK(next_);
next_ = next_->next_;
}
private:
Member<LinkedNode> next_;
};
class MutatingPrefinalizer final
: public GarbageCollected<MutatingPrefinalizer> {
CPPGC_USING_PRE_FINALIZER(MutatingPrefinalizer, PreFinalizer);
public:
void PreFinalizer() {
// Pre-finalizers are generally used to mutate the object graph. The API
// does not allow distinguishing between live and dead objects. It is
// generally safe to re-write the dead *or* the live object graph. Adding
// a dead object to the live graph must not happen.
//
// RemoveNext() must not trigger a write barrier. In the case all LinkedNode
// objects die at the same time, the graph is mutated with a dead object.
// This is only safe when the dead object is added to a dead subgraph.
parent_node_->RemoveNext();
}
explicit MutatingPrefinalizer(LinkedNode* parent) : parent_node_(parent) {}
void Trace(Visitor* visitor) const { visitor->Trace(parent_node_); }
private:
Member<LinkedNode> parent_node_;
};
} // namespace
TEST_F(PrefinalizerTest, PrefinalizerCanRewireGraphWithLiveObjects) {
Persistent<LinkedNode> root{MakeGarbageCollected<LinkedNode>(
GetAllocationHandle(),
MakeGarbageCollected<LinkedNode>(
GetAllocationHandle(),
MakeGarbageCollected<LinkedNode>(GetAllocationHandle(), nullptr)))};
CHECK(root->next());
MakeGarbageCollected<MutatingPrefinalizer>(GetAllocationHandle(), root.Get());
PreciseGC();
}
namespace {
class PrefinalizerDeathTest : public testing::TestWithHeap {};
class AllocatingPrefinalizer : public GarbageCollected<AllocatingPrefinalizer> {
CPPGC_USING_PRE_FINALIZER(AllocatingPrefinalizer, PreFinalizer);
public:
explicit AllocatingPrefinalizer(cppgc::Heap* heap) : heap_(heap) {}
void Trace(Visitor*) const {}
void PreFinalizer() {
MakeGarbageCollected<GCed>(heap_->GetAllocationHandle());
}
private:
cppgc::Heap* heap_;
};
} // namespace
#ifdef CPPGC_ALLOW_ALLOCATIONS_IN_PREFINALIZERS
TEST_F(PrefinalizerTest, PrefinalizerDoesNotFailOnAllcoation) {
auto* object = MakeGarbageCollected<AllocatingPrefinalizer>(
GetAllocationHandle(), GetHeap());
PreciseGC();
USE(object);
}
#else
#ifdef DEBUG
TEST_F(PrefinalizerDeathTest, PrefinalizerFailsOnAllcoation) {
auto* object = MakeGarbageCollected<AllocatingPrefinalizer>(
GetAllocationHandle(), GetHeap());
USE(object);
EXPECT_DEATH_IF_SUPPORTED(PreciseGC(), "");
}
#endif // DEBUG
#endif // CPPGC_ALLOW_ALLOCATIONS_IN_PREFINALIZERS
namespace {
template <template <typename T> class RefType>
class RessurectingPrefinalizer
: public GarbageCollected<RessurectingPrefinalizer<RefType>> {
CPPGC_USING_PRE_FINALIZER(RessurectingPrefinalizer, PreFinalizer);
public:
explicit RessurectingPrefinalizer(RefType<GCed>& ref, GCed* obj)
: ref_(reinterpret_cast<void*>(&ref)), obj_(obj) {}
void Trace(Visitor*) const {}
void PreFinalizer() { *reinterpret_cast<RefType<GCed>*>(ref_) = obj_; }
private:
void* const ref_;
const UntracedMember<GCed> obj_;
};
class GCedHolder : public GarbageCollected<GCedHolder> {
public:
void Trace(Visitor* v) const { v->Trace(member_); }
Member<GCed> member_;
};
} // namespace
#if DEBUG
#ifdef CPPGC_VERIFY_HEAP
TEST_F(PrefinalizerDeathTest, PrefinalizerCanRewireGraphWithDeadObjects) {
// Prefinalizers are allowed to rewire dead object to dead objects as that
// doesn't affect the live object graph.
Persistent<LinkedNode> root{MakeGarbageCollected<LinkedNode>(
GetAllocationHandle(),
MakeGarbageCollected<LinkedNode>(
GetAllocationHandle(),
MakeGarbageCollected<LinkedNode>(GetAllocationHandle(), nullptr)))};
CHECK(root->next());
MakeGarbageCollected<MutatingPrefinalizer>(GetAllocationHandle(), root.Get());
// All LinkedNode objects will die on the following GC. The pre-finalizer may
// still operate with them but not add them to a live object.
root.Clear();
PreciseGC();
}
#ifdef CPPGC_ENABLE_SLOW_API_CHECKS
TEST_F(PrefinalizerDeathTest, PrefinalizerCantRessurectObjectOnStack) {
Persistent<GCed> persistent;
MakeGarbageCollected<RessurectingPrefinalizer<Persistent>>(
GetAllocationHandle(), persistent,
MakeGarbageCollected<GCed>(GetAllocationHandle()));
EXPECT_DEATH_IF_SUPPORTED(PreciseGC(), "");
}
#endif // CPPGC_ENABLE_SLOW_API_CHECKS
TEST_F(PrefinalizerDeathTest, PrefinalizerCantRessurectObjectOnHeap) {
Persistent<GCedHolder> persistent(
MakeGarbageCollected<GCedHolder>(GetAllocationHandle()));
MakeGarbageCollected<RessurectingPrefinalizer<Member>>(
GetAllocationHandle(), persistent->member_,
MakeGarbageCollected<GCed>(GetAllocationHandle()));
EXPECT_DEATH_IF_SUPPORTED(PreciseGC(), "");
}
#endif // CPPGC_VERIFY_HEAP
#endif // DEBUG
#ifdef CPPGC_ALLOW_ALLOCATIONS_IN_PREFINALIZERS
TEST_F(PrefinalizerTest, AllocatingPrefinalizersInMultipleGCCycles) {
auto* object = MakeGarbageCollected<AllocatingPrefinalizer>(
GetAllocationHandle(), GetHeap());
PreciseGC();
auto* other_object = MakeGarbageCollected<AllocatingPrefinalizer>(
GetAllocationHandle(), GetHeap());
PreciseGC();
USE(object);
USE(other_object);
}
#endif
class GCedBase : public GarbageCollected<GCedBase> {
CPPGC_USING_PRE_FINALIZER(GCedBase, PreFinalize);
public:
void Trace(Visitor*) const {}
virtual void PreFinalize() { ++prefinalizer_count_; }
static size_t prefinalizer_count_;
};
size_t GCedBase::prefinalizer_count_ = 0u;
class GCedInherited : public GCedBase {
public:
void PreFinalize() override { ++prefinalizer_count_; }
static size_t prefinalizer_count_;
};
size_t GCedInherited::prefinalizer_count_ = 0u;
TEST_F(PrefinalizerTest, VirtualPrefinalizer) {
MakeGarbageCollected<GCedInherited>(GetAllocationHandle());
GCedBase::prefinalizer_count_ = 0u;
GCedInherited::prefinalizer_count_ = 0u;
PreciseGC();
EXPECT_EQ(0u, GCedBase::prefinalizer_count_);
EXPECT_LT(0u, GCedInherited::prefinalizer_count_);
}
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,37 @@
// 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/platform.h"
#include "src/base/page-allocator.h"
#include "test/unittests/heap/cppgc/test-platform.h"
#include "testing/gmock/include/gmock/gmock.h"
namespace {
class CppGCEnvironment final : public ::testing::Environment {
public:
void SetUp() override {
// Initialize the process for cppgc with an arbitrary page allocator. This
// has to survive as long as the process, so it's ok to leak the allocator
// here.
cppgc::InitializeProcess(new v8::base::PageAllocator());
}
void TearDown() override { cppgc::ShutdownProcess(); }
};
} // namespace
int main(int argc, char** argv) {
// Don't catch SEH exceptions and continue as the following tests might hang
// in an broken environment on windows.
testing::GTEST_FLAG(catch_exceptions) = false;
// Most unit-tests are multi-threaded, so enable thread-safe death-tests.
testing::FLAGS_gtest_death_test_style = "threadsafe";
testing::InitGoogleMock(&argc, argv);
testing::AddGlobalTestEnvironment(new CppGCEnvironment);
return RUN_ALL_TESTS();
}

View File

@ -0,0 +1,59 @@
// Copyright 2021 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/base/macros.h"
#include "src/base/sanitizer/asan.h"
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gtest/include/gtest/gtest.h"
#if defined(LEAK_SANITIZER)
#include <sanitizer/lsan_interface.h>
#endif // LEAK_SANITIZER
namespace cppgc {
namespace internal {
#if defined(LEAK_SANITIZER)
using LsanTest = testing::TestWithHeap;
class GCed final : public GarbageCollected<GCed> {
public:
void Trace(cppgc::Visitor*) const {}
std::unique_ptr<int> dummy{std::make_unique<int>(17)};
};
TEST_F(LsanTest, LeakDetectionDoesNotFindMemoryRetainedFromManaged) {
auto* o = MakeGarbageCollected<GCed>(GetAllocationHandle());
__lsan_do_leak_check();
USE(o);
}
#endif // LEAK_SANITIZER
#ifdef V8_USE_ADDRESS_SANITIZER
using AsanTest = testing::TestWithHeap;
class ObjectPoisoningInDestructor final
: public GarbageCollected<ObjectPoisoningInDestructor> {
public:
~ObjectPoisoningInDestructor() {
ASAN_POISON_MEMORY_REGION(this, sizeof(ObjectPoisoningInDestructor));
}
void Trace(cppgc::Visitor*) const {}
void* dummy{0};
};
TEST_F(AsanTest, ObjectPoisoningInDestructor) {
MakeGarbageCollected<ObjectPoisoningInDestructor>(GetAllocationHandle());
PreciseGC();
}
#endif // V8_USE_ADDRESS_SANITIZER
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,61 @@
// 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/source-location.h"
#include "src/base/macros.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace {
constexpr char kFileName[] = "source-location-unittest.cc";
bool Contains(const std::string& base_string, const std::string& substring) {
return base_string.find(substring) != std::string::npos;
}
} // namespace
TEST(SourceLocationTest, DefaultCtor) {
constexpr SourceLocation loc;
EXPECT_EQ(nullptr, loc.Function());
EXPECT_EQ(nullptr, loc.FileName());
EXPECT_EQ(0u, loc.Line());
}
void TestSourceLocationCurrent() {
static constexpr char kFunctionName[] = "TestSourceLocationCurrent";
static constexpr size_t kNextLine = __LINE__ + 1;
constexpr auto loc = SourceLocation::Current();
#if !V8_SUPPORTS_SOURCE_LOCATION
EXPECT_EQ(nullptr, loc.Function());
EXPECT_EQ(nullptr, loc.FileName());
EXPECT_EQ(0u, loc.Line());
USE(kNextLine);
return;
#endif
EXPECT_EQ(kNextLine, loc.Line());
EXPECT_TRUE(Contains(loc.FileName(), kFileName));
EXPECT_TRUE(Contains(loc.Function(), kFunctionName));
}
TEST(SourceLocationTest, Current) { TestSourceLocationCurrent(); }
void TestToString() {
static const std::string kDescriptor = std::string(__func__) + "@" +
__FILE__ + ":" +
std::to_string(__LINE__ + 1);
constexpr auto loc = SourceLocation::Current();
const auto string = loc.ToString();
EXPECT_EQ(kDescriptor, string);
}
#if V8_SUPPORTS_SOURCE_LOCATION
TEST(SourceLocationTest, ToString) { TestToString(); }
#endif
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,474 @@
// 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/base/stack.h"
#include <memory>
#include <ostream>
#include "include/v8config.h"
#include "testing/gtest/include/gtest/gtest.h"
#if V8_OS_LINUX && (V8_HOST_ARCH_IA32 || V8_HOST_ARCH_X64)
#include <xmmintrin.h>
#endif
namespace cppgc {
namespace internal {
using heap::base::Stack;
using heap::base::StackVisitor;
namespace {
class GCStackTest : public ::testing::Test {
public:
GCStackTest() : stack_(std::make_unique<Stack>()) { stack_->SetStackStart(); }
Stack* GetStack() const { return stack_.get(); }
private:
std::unique_ptr<Stack> stack_;
};
} // namespace
#if !V8_OS_FUCHSIA
TEST_F(GCStackTest, IsOnStackForStackValue) {
void* dummy;
EXPECT_TRUE(GetStack()->IsOnStack(&dummy));
}
#endif // !V8_OS_FUCHSIA
TEST_F(GCStackTest, IsOnStackForHeapValue) {
auto dummy = std::make_unique<int>();
EXPECT_FALSE(GetStack()->IsOnStack(dummy.get()));
}
namespace {
class StackScanner final : public StackVisitor {
public:
struct Container {
std::unique_ptr<int> value;
};
StackScanner() : container_(new Container{}) {
container_->value = std::make_unique<int>();
}
void VisitPointer(const void* address) final {
if (address == container_->value.get()) found_ = true;
}
void Reset() { found_ = false; }
bool found() const { return found_; }
int* needle() const { return container_->value.get(); }
private:
std::unique_ptr<Container> container_;
bool found_ = false;
};
} // namespace
TEST_F(GCStackTest, IteratePointersFindsOnStackValue) {
auto scanner = std::make_unique<StackScanner>();
// No check that the needle is initially not found as on some platforms it
// may be part of temporaries after setting it up through StackScanner.
{
int* volatile tmp = scanner->needle();
USE(tmp);
GetStack()->IteratePointersForTesting(scanner.get());
EXPECT_TRUE(scanner->found());
}
}
TEST_F(GCStackTest, IteratePointersFindsOnStackValuePotentiallyUnaligned) {
auto scanner = std::make_unique<StackScanner>();
// No check that the needle is initially not found as on some platforms it
// may be part of temporaries after setting it up through StackScanner.
{
char a = 'c';
USE(a);
int* volatile tmp = scanner->needle();
USE(tmp);
GetStack()->IteratePointersForTesting(scanner.get());
EXPECT_TRUE(scanner->found());
}
}
namespace {
// Prevent inlining as that would allow the compiler to prove that the parameter
// must not actually be materialized.
//
// Parameter positions are explicit to test various calling conventions.
V8_NOINLINE void* RecursivelyPassOnParameterImpl(void* p1, void* p2, void* p3,
void* p4, void* p5, void* p6,
void* p7, void* p8,
Stack* stack,
StackVisitor* visitor) {
if (p1) {
return RecursivelyPassOnParameterImpl(nullptr, p1, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
stack, visitor);
} else if (p2) {
return RecursivelyPassOnParameterImpl(nullptr, nullptr, p2, nullptr,
nullptr, nullptr, nullptr, nullptr,
stack, visitor);
} else if (p3) {
return RecursivelyPassOnParameterImpl(nullptr, nullptr, nullptr, p3,
nullptr, nullptr, nullptr, nullptr,
stack, visitor);
} else if (p4) {
return RecursivelyPassOnParameterImpl(nullptr, nullptr, nullptr, nullptr,
p4, nullptr, nullptr, nullptr, stack,
visitor);
} else if (p5) {
return RecursivelyPassOnParameterImpl(nullptr, nullptr, nullptr, nullptr,
nullptr, p5, nullptr, nullptr, stack,
visitor);
} else if (p6) {
return RecursivelyPassOnParameterImpl(nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, p6, nullptr, stack,
visitor);
} else if (p7) {
return RecursivelyPassOnParameterImpl(nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, p7, stack,
visitor);
} else if (p8) {
stack->IteratePointersForTesting(visitor);
return p8;
}
return nullptr;
}
V8_NOINLINE void* RecursivelyPassOnParameter(size_t num, void* parameter,
Stack* stack,
StackVisitor* visitor) {
switch (num) {
case 0:
stack->IteratePointersForTesting(visitor);
return parameter;
case 1:
return RecursivelyPassOnParameterImpl(nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr,
parameter, stack, visitor);
case 2:
return RecursivelyPassOnParameterImpl(nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, parameter,
nullptr, stack, visitor);
case 3:
return RecursivelyPassOnParameterImpl(nullptr, nullptr, nullptr, nullptr,
nullptr, parameter, nullptr,
nullptr, stack, visitor);
case 4:
return RecursivelyPassOnParameterImpl(nullptr, nullptr, nullptr, nullptr,
parameter, nullptr, nullptr,
nullptr, stack, visitor);
case 5:
return RecursivelyPassOnParameterImpl(nullptr, nullptr, nullptr,
parameter, nullptr, nullptr,
nullptr, nullptr, stack, visitor);
case 6:
return RecursivelyPassOnParameterImpl(nullptr, nullptr, parameter,
nullptr, nullptr, nullptr, nullptr,
nullptr, stack, visitor);
case 7:
return RecursivelyPassOnParameterImpl(nullptr, parameter, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, stack, visitor);
case 8:
return RecursivelyPassOnParameterImpl(parameter, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, stack, visitor);
default:
UNREACHABLE();
}
UNREACHABLE();
}
} // namespace
TEST_F(GCStackTest, IteratePointersFindsParameterNesting0) {
auto scanner = std::make_unique<StackScanner>();
void* needle = RecursivelyPassOnParameter(0, scanner->needle(), GetStack(),
scanner.get());
EXPECT_EQ(scanner->needle(), needle);
EXPECT_TRUE(scanner->found());
}
TEST_F(GCStackTest, IteratePointersFindsParameterNesting1) {
auto scanner = std::make_unique<StackScanner>();
void* needle = RecursivelyPassOnParameter(1, scanner->needle(), GetStack(),
scanner.get());
EXPECT_EQ(scanner->needle(), needle);
EXPECT_TRUE(scanner->found());
}
TEST_F(GCStackTest, IteratePointersFindsParameterNesting2) {
auto scanner = std::make_unique<StackScanner>();
void* needle = RecursivelyPassOnParameter(2, scanner->needle(), GetStack(),
scanner.get());
EXPECT_EQ(scanner->needle(), needle);
EXPECT_TRUE(scanner->found());
}
TEST_F(GCStackTest, IteratePointersFindsParameterNesting3) {
auto scanner = std::make_unique<StackScanner>();
void* needle = RecursivelyPassOnParameter(3, scanner->needle(), GetStack(),
scanner.get());
EXPECT_EQ(scanner->needle(), needle);
EXPECT_TRUE(scanner->found());
}
TEST_F(GCStackTest, IteratePointersFindsParameterNesting4) {
auto scanner = std::make_unique<StackScanner>();
void* needle = RecursivelyPassOnParameter(4, scanner->needle(), GetStack(),
scanner.get());
EXPECT_EQ(scanner->needle(), needle);
EXPECT_TRUE(scanner->found());
}
TEST_F(GCStackTest, IteratePointersFindsParameterNesting5) {
auto scanner = std::make_unique<StackScanner>();
void* needle = RecursivelyPassOnParameter(5, scanner->needle(), GetStack(),
scanner.get());
EXPECT_EQ(scanner->needle(), needle);
EXPECT_TRUE(scanner->found());
}
TEST_F(GCStackTest, IteratePointersFindsParameterNesting6) {
auto scanner = std::make_unique<StackScanner>();
void* needle = RecursivelyPassOnParameter(6, scanner->needle(), GetStack(),
scanner.get());
EXPECT_EQ(scanner->needle(), needle);
EXPECT_TRUE(scanner->found());
}
TEST_F(GCStackTest, IteratePointersFindsParameterNesting7) {
auto scanner = std::make_unique<StackScanner>();
void* needle = RecursivelyPassOnParameter(7, scanner->needle(), GetStack(),
scanner.get());
EXPECT_EQ(scanner->needle(), needle);
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(),
scanner.get());
EXPECT_EQ(scanner->needle(), needle);
EXPECT_TRUE(scanner->found());
}
#endif // !_MSC_VER || __clang__
namespace {
// We manually call into this function from inline assembly. Therefore we need
// to make sure that:
// 1) there is no .plt indirection (i.e. visibility is hidden);
// 2) stack is realigned in the function prologue.
extern "C" V8_NOINLINE
#if defined(__clang__)
__attribute__((used))
#if !defined(V8_OS_WIN)
__attribute__((visibility("hidden")))
#endif // !defined(V8_OS_WIN)
#ifdef __has_attribute
#if __has_attribute(force_align_arg_pointer)
__attribute__((force_align_arg_pointer))
#endif // __has_attribute(force_align_arg_pointer)
#endif // __has_attribute
#endif // defined(__clang__)
void
IteratePointersNoMangling(Stack* stack, StackVisitor* visitor) {
stack->IteratePointersForTesting(visitor);
}
} // namespace
// The following tests use inline assembly and have been checked to work on
// clang to verify that the stack-scanning trampoline pushes callee-saved
// registers.
//
// The test uses a macro loop as asm() can only be passed string literals.
#ifdef __clang__
#ifdef V8_TARGET_ARCH_X64
#ifdef V8_OS_WIN
// Excluded from test: rbp
#define FOR_ALL_CALLEE_SAVED_REGS(V) \
V("rdi") \
V("rsi") \
V("rbx") \
V("r12") \
V("r13") \
V("r14") \
V("r15")
#else // !V8_OS_WIN
// Excluded from test: rbp
#define FOR_ALL_CALLEE_SAVED_REGS(V) \
V("rbx") \
V("r12") \
V("r13") \
V("r14") \
V("r15")
#endif // !V8_OS_WIN
#endif // V8_TARGET_ARCH_X64
#endif // __clang__
#ifdef FOR_ALL_CALLEE_SAVED_REGS
TEST_F(GCStackTest, IteratePointersFindsCalleeSavedRegisters) {
auto scanner = std::make_unique<StackScanner>();
// No check that the needle is initially not found as on some platforms it
// may be part of temporaries after setting it up through StackScanner.
// First, clear all callee-saved registers.
#define CLEAR_REGISTER(reg) asm("mov $0, %%" reg : : : reg);
FOR_ALL_CALLEE_SAVED_REGS(CLEAR_REGISTER)
#undef CLEAR_REGISTER
// Keep local raw pointers to keep instruction sequences small below.
auto* local_stack = GetStack();
auto* local_scanner = scanner.get();
#define MOVE_TO_REG_AND_CALL_IMPL(needle_reg, arg1, arg2) \
asm volatile("mov %0, %%" needle_reg "\n mov %1, %%" arg1 \
"\n mov %2, %%" arg2 \
"\n call %P3" \
"\n mov $0, %%" needle_reg \
: \
: "r"(local_scanner->needle()), "r"(local_stack), \
"r"(local_scanner), "i"(IteratePointersNoMangling) \
: "memory", needle_reg, arg1, arg2, "cc");
#ifdef V8_OS_WIN
#define MOVE_TO_REG_AND_CALL(reg) MOVE_TO_REG_AND_CALL_IMPL(reg, "rcx", "rdx")
#else // !V8_OS_WIN
#define MOVE_TO_REG_AND_CALL(reg) MOVE_TO_REG_AND_CALL_IMPL(reg, "rdi", "rsi")
#endif // V8_OS_WIN
// Moves |local_scanner->needle()| into a callee-saved register, leaving the
// callee-saved register as the only register referencing the needle.
// (Ignoring implementation-dependent dirty registers/stack.)
#define KEEP_ALIVE_FROM_CALLEE_SAVED(reg) \
local_scanner->Reset(); \
/* Wrap the inline assembly in a lambda to rely on the compiler for saving \
caller-saved registers. */ \
[local_stack, local_scanner]() V8_NOINLINE { MOVE_TO_REG_AND_CALL(reg) }(); \
EXPECT_TRUE(local_scanner->found()) \
<< "pointer in callee-saved register not found. register: " << reg \
<< std::endl;
FOR_ALL_CALLEE_SAVED_REGS(KEEP_ALIVE_FROM_CALLEE_SAVED)
#undef MOVE_TO_REG_AND_CALL
#undef MOVE_TO_REG_AND_CALL_IMPL
#undef KEEP_ALIVE_FROM_CALLEE_SAVED
#undef FOR_ALL_CALLEE_SAVED_REGS
}
#endif // FOR_ALL_CALLEE_SAVED_REGS
#if defined(__clang__) && defined(V8_TARGET_ARCH_X64) && defined(V8_OS_WIN)
#define FOR_ALL_XMM_CALLEE_SAVED_REGS(V) \
V("xmm6") \
V("xmm7") \
V("xmm8") \
V("xmm9") \
V("xmm10") \
V("xmm11") \
V("xmm12") \
V("xmm13") \
V("xmm14") \
V("xmm15")
TEST_F(GCStackTest, IteratePointersFindsCalleeSavedXMMRegisters) {
auto scanner = std::make_unique<StackScanner>();
// No check that the needle is initially not found as on some platforms it
// may be part of temporaries after setting it up through StackScanner.
// First, clear all callee-saved xmm registers.
#define CLEAR_REGISTER(reg) asm("pxor %%" reg ", %%" reg : : : reg);
FOR_ALL_XMM_CALLEE_SAVED_REGS(CLEAR_REGISTER)
#undef CLEAR_REGISTER
// Keep local raw pointers to keep instruction sequences small below.
auto* local_stack = GetStack();
auto* local_scanner = scanner.get();
// Moves |local_scanner->needle()| into a callee-saved register, leaving the
// callee-saved register as the only register referencing the needle.
// (Ignoring implementation-dependent dirty registers/stack.)
#define KEEP_ALIVE_FROM_CALLEE_SAVED(reg) \
local_scanner->Reset(); \
[local_stack, local_scanner]() V8_NOINLINE { MOVE_TO_REG_AND_CALL(reg) }(); \
EXPECT_TRUE(local_scanner->found()) \
<< "pointer in callee-saved xmm register not found. register: " << reg \
<< std::endl;
// First, test the pointer in the low quadword.
#define MOVE_TO_REG_AND_CALL(reg) \
asm volatile("mov %0, %%rax \n movq %%rax, %%" reg \
"\n mov %1, %%rcx \n mov %2, %%rdx" \
"\n call %P3" \
"\n pxor %%" reg ", %%" reg \
: \
: "r"(local_scanner->needle()), "r"(local_stack), \
"r"(local_scanner), "i"(IteratePointersNoMangling) \
: "memory", "rax", reg, "rcx", "rdx", "cc");
FOR_ALL_XMM_CALLEE_SAVED_REGS(KEEP_ALIVE_FROM_CALLEE_SAVED)
#undef MOVE_TO_REG_AND_CALL
// Then, test the pointer in the upper quadword.
#define MOVE_TO_REG_AND_CALL(reg) \
asm volatile("mov %0, %%rax \n movq %%rax, %%" reg \
"\n pshufd $0b01001110, %%" reg ", %%" reg \
"\n mov %1, %%rcx \n mov %2, %%rdx" \
"\n call %P3" \
"\n pxor %%" reg ", %%" reg \
: \
: "r"(local_scanner->needle()), "r"(local_stack), \
"r"(local_scanner), "i"(IteratePointersNoMangling) \
: "memory", "rax", reg, "rcx", "rdx", "cc");
FOR_ALL_XMM_CALLEE_SAVED_REGS(KEEP_ALIVE_FROM_CALLEE_SAVED)
#undef MOVE_TO_REG_AND_CALL
#undef KEEP_ALIVE_FROM_CALLEE_SAVED
#undef FOR_ALL_XMM_CALLEE_SAVED_REGS
}
#endif // defined(__clang__) && defined(V8_TARGET_ARCH_X64) &&
// defined(V8_OS_WIN)
#if V8_OS_LINUX && (V8_HOST_ARCH_IA32 || V8_HOST_ARCH_X64)
class CheckStackAlignmentVisitor final : public StackVisitor {
public:
void VisitPointer(const void*) final {
float f[4] = {0.};
volatile auto xmm = ::_mm_load_ps(f);
USE(xmm);
}
};
TEST_F(GCStackTest, StackAlignment) {
auto checker = std::make_unique<CheckStackAlignmentVisitor>();
GetStack()->IteratePointersForTesting(checker.get());
}
#endif // V8_OS_LINUX && (V8_HOST_ARCH_IA32 || V8_HOST_ARCH_X64)
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,314 @@
// 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.
#if CPPGC_IS_STANDALONE
#include "src/heap/cppgc/heap-config.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 DelegatingTracingControllerImpl : public TracingController {
public:
virtual uint64_t AddTraceEvent(
char phase, const uint8_t* category_enabled_flag, const char* name,
const char* scope, uint64_t id, uint64_t bind_id, int32_t num_args,
const char** arg_names, const uint8_t* arg_types,
const uint64_t* arg_values,
std::unique_ptr<ConvertableToTraceFormat>* arg_convertables,
unsigned int flags) {
if (!check_expectations) return 0;
static char phases[2] = {'B', 'E'};
EXPECT_EQ(phases[AddTraceEvent_callcount], phase);
EXPECT_TRUE(*category_enabled_flag);
if (expected_name) {
EXPECT_EQ(0, strcmp(expected_name, name));
}
stored_num_args += num_args;
for (int i = 0; i < num_args; ++i) {
stored_arg_names.push_back(arg_names[i]);
stored_arg_types.push_back(arg_types[i]);
stored_arg_values.push_back(arg_values[i]);
}
AddTraceEvent_callcount++;
return 0;
}
static bool check_expectations;
static size_t AddTraceEvent_callcount;
static const char* expected_name;
static int32_t stored_num_args;
static std::vector<std::string> stored_arg_names;
static std::vector<uint8_t> stored_arg_types;
static std::vector<uint64_t> stored_arg_values;
};
bool DelegatingTracingControllerImpl::check_expectations = false;
size_t DelegatingTracingControllerImpl::AddTraceEvent_callcount = 0u;
const char* DelegatingTracingControllerImpl::expected_name = nullptr;
int32_t DelegatingTracingControllerImpl::stored_num_args = 0;
std::vector<std::string> DelegatingTracingControllerImpl::stored_arg_names;
std::vector<uint8_t> DelegatingTracingControllerImpl::stored_arg_types;
std::vector<uint64_t> DelegatingTracingControllerImpl::stored_arg_values;
class V8_NODISCARD CppgcTracingScopesTest : public testing::TestWithHeap {
public:
CppgcTracingScopesTest() {
SetTracingController(std::make_unique<DelegatingTracingControllerImpl>());
}
void StartGC() {
MarkingConfig config = {CollectionType::kMajor, StackState::kNoHeapPointers,
GCConfig::MarkingType::kIncremental};
GetMarkerRef() = std::make_unique<Marker>(
Heap::From(GetHeap())->AsBase(), GetPlatformHandle().get(), config);
GetMarkerRef()->StartMarking();
DelegatingTracingControllerImpl::check_expectations = true;
}
void EndGC() {
DelegatingTracingControllerImpl::check_expectations = false;
GetMarkerRef()->FinishMarking(StackState::kNoHeapPointers);
GetMarkerRef().reset();
Heap::From(GetHeap())->stats_collector()->NotifySweepingCompleted(
GCConfig::SweepingType::kAtomic);
}
void ResetDelegatingTracingController(const char* expected_name = nullptr) {
DelegatingTracingControllerImpl::AddTraceEvent_callcount = 0u;
DelegatingTracingControllerImpl::stored_num_args = 0;
DelegatingTracingControllerImpl::stored_arg_names.clear();
DelegatingTracingControllerImpl::stored_arg_types.clear();
DelegatingTracingControllerImpl::stored_arg_values.clear();
DelegatingTracingControllerImpl::expected_name = expected_name;
}
void FindArgument(std::string name, uint8_t type, uint64_t value) {
int i = 0;
for (; i < DelegatingTracingControllerImpl::stored_num_args; ++i) {
if (name.compare(DelegatingTracingControllerImpl::stored_arg_names[i]) ==
0)
break;
}
EXPECT_LT(i, DelegatingTracingControllerImpl::stored_num_args);
EXPECT_EQ(type, DelegatingTracingControllerImpl::stored_arg_types[i]);
EXPECT_EQ(value, DelegatingTracingControllerImpl::stored_arg_values[i]);
}
};
} // namespace
TEST_F(CppgcTracingScopesTest, DisabledScope) {
StartGC();
ResetDelegatingTracingController();
{
StatsCollector::DisabledScope scope(
Heap::From(GetHeap())->stats_collector(),
StatsCollector::kMarkProcessMarkingWorklist);
}
EXPECT_EQ(0u, DelegatingTracingControllerImpl::AddTraceEvent_callcount);
EndGC();
}
TEST_F(CppgcTracingScopesTest, EnabledScope) {
{
StartGC();
ResetDelegatingTracingController("CppGC.MarkProcessMarkingWorklist");
{
StatsCollector::EnabledScope scope(
Heap::From(GetHeap())->stats_collector(),
StatsCollector::kMarkProcessMarkingWorklist);
}
EXPECT_EQ(2u, DelegatingTracingControllerImpl::AddTraceEvent_callcount);
EndGC();
}
{
StartGC();
ResetDelegatingTracingController("CppGC.MarkProcessWriteBarrierWorklist");
{
StatsCollector::EnabledScope scope(
Heap::From(GetHeap())->stats_collector(),
StatsCollector::kMarkProcessWriteBarrierWorklist);
}
EXPECT_EQ(2u, DelegatingTracingControllerImpl::AddTraceEvent_callcount);
EndGC();
}
}
TEST_F(CppgcTracingScopesTest, EnabledScopeWithArgs) {
// Scopes always add 2 arguments: epoch and is_forced_gc.
{
StartGC();
ResetDelegatingTracingController();
{
StatsCollector::EnabledScope scope(
Heap::From(GetHeap())->stats_collector(),
StatsCollector::kMarkProcessMarkingWorklist);
}
EXPECT_EQ(2, DelegatingTracingControllerImpl::stored_num_args);
EndGC();
}
{
StartGC();
ResetDelegatingTracingController();
{
StatsCollector::EnabledScope scope(
Heap::From(GetHeap())->stats_collector(),
StatsCollector::kMarkProcessMarkingWorklist, "arg1", 1);
}
EXPECT_EQ(3, DelegatingTracingControllerImpl::stored_num_args);
EndGC();
}
{
StartGC();
ResetDelegatingTracingController();
{
StatsCollector::EnabledScope scope(
Heap::From(GetHeap())->stats_collector(),
StatsCollector::kMarkProcessMarkingWorklist, "arg1", 1, "arg2", 2);
}
EXPECT_EQ(4, DelegatingTracingControllerImpl::stored_num_args);
EndGC();
}
}
TEST_F(CppgcTracingScopesTest, CheckScopeArgs) {
{
StartGC();
ResetDelegatingTracingController();
{
StatsCollector::EnabledScope scope(
Heap::From(GetHeap())->stats_collector(),
StatsCollector::kMarkProcessMarkingWorklist, "uint_arg", 13u,
"bool_arg", false);
}
FindArgument("uint_arg", TRACE_VALUE_TYPE_UINT, 13);
FindArgument("bool_arg", TRACE_VALUE_TYPE_BOOL, false);
EndGC();
}
{
StartGC();
ResetDelegatingTracingController();
{
StatsCollector::EnabledScope scope(
Heap::From(GetHeap())->stats_collector(),
StatsCollector::kMarkProcessMarkingWorklist, "neg_int_arg", -5,
"pos_int_arg", 7);
}
FindArgument("neg_int_arg", TRACE_VALUE_TYPE_INT, -5);
FindArgument("pos_int_arg", TRACE_VALUE_TYPE_INT, 7);
EndGC();
}
{
StartGC();
ResetDelegatingTracingController();
double double_value = 1.2;
const char* string_value = "test";
{
StatsCollector::EnabledScope scope(
Heap::From(GetHeap())->stats_collector(),
StatsCollector::kMarkProcessMarkingWorklist, "string_arg",
string_value, "double_arg", double_value);
}
FindArgument("string_arg", TRACE_VALUE_TYPE_STRING,
reinterpret_cast<uint64_t>(string_value));
FindArgument("double_arg", TRACE_VALUE_TYPE_DOUBLE,
*reinterpret_cast<uint64_t*>(&double_value));
EndGC();
}
}
TEST_F(CppgcTracingScopesTest, InitalScopesAreZero) {
StatsCollector* stats_collector = Heap::From(GetHeap())->stats_collector();
stats_collector->NotifyMarkingStarted(CollectionType::kMajor,
GCConfig::MarkingType::kAtomic,
GCConfig::IsForcedGC::kNotForced);
stats_collector->NotifyMarkingCompleted(0);
stats_collector->NotifySweepingCompleted(GCConfig::SweepingType::kAtomic);
const StatsCollector::Event& event =
stats_collector->GetPreviousEventForTesting();
for (int i = 0; i < StatsCollector::kNumHistogramScopeIds; ++i) {
EXPECT_TRUE(event.scope_data[i].IsZero());
}
for (int i = 0; i < StatsCollector::kNumHistogramConcurrentScopeIds; ++i) {
EXPECT_EQ(0, event.concurrent_scope_data[i]);
}
}
TEST_F(CppgcTracingScopesTest, TestIndividualScopes) {
for (int scope_id = 0; scope_id < StatsCollector::kNumHistogramScopeIds;
++scope_id) {
StatsCollector* stats_collector = Heap::From(GetHeap())->stats_collector();
stats_collector->NotifyMarkingStarted(CollectionType::kMajor,
GCConfig::MarkingType::kIncremental,
GCConfig::IsForcedGC::kNotForced);
DelegatingTracingControllerImpl::check_expectations = false;
{
StatsCollector::EnabledScope scope(
Heap::From(GetHeap())->stats_collector(),
static_cast<StatsCollector::ScopeId>(scope_id));
v8::base::TimeTicks time = v8::base::TimeTicks::Now();
while (time == v8::base::TimeTicks::Now()) {
// Force time to progress before destroying scope.
}
}
stats_collector->NotifyMarkingCompleted(0);
stats_collector->NotifySweepingCompleted(
GCConfig::SweepingType::kIncremental);
const StatsCollector::Event& event =
stats_collector->GetPreviousEventForTesting();
for (int i = 0; i < StatsCollector::kNumHistogramScopeIds; ++i) {
if (i == scope_id)
EXPECT_LT(v8::base::TimeDelta(), event.scope_data[i]);
else
EXPECT_TRUE(event.scope_data[i].IsZero());
}
for (int i = 0; i < StatsCollector::kNumHistogramConcurrentScopeIds; ++i) {
EXPECT_EQ(0, event.concurrent_scope_data[i]);
}
}
}
TEST_F(CppgcTracingScopesTest, TestIndividualConcurrentScopes) {
for (int scope_id = 0;
scope_id < StatsCollector::kNumHistogramConcurrentScopeIds; ++scope_id) {
StatsCollector* stats_collector = Heap::From(GetHeap())->stats_collector();
stats_collector->NotifyMarkingStarted(CollectionType::kMajor,
GCConfig::MarkingType::kAtomic,
GCConfig::IsForcedGC::kNotForced);
DelegatingTracingControllerImpl::check_expectations = false;
{
StatsCollector::EnabledConcurrentScope scope(
Heap::From(GetHeap())->stats_collector(),
static_cast<StatsCollector::ConcurrentScopeId>(scope_id));
v8::base::TimeTicks time = v8::base::TimeTicks::Now();
while (time == v8::base::TimeTicks::Now()) {
// Force time to progress before destroying scope.
}
}
stats_collector->NotifyMarkingCompleted(0);
stats_collector->NotifySweepingCompleted(GCConfig::SweepingType::kAtomic);
const StatsCollector::Event& event =
stats_collector->GetPreviousEventForTesting();
for (int i = 0; i < StatsCollector::kNumHistogramScopeIds; ++i) {
EXPECT_TRUE(event.scope_data[i].IsZero());
}
for (int i = 0; i < StatsCollector::kNumHistogramConcurrentScopeIds; ++i) {
if (i == scope_id)
EXPECT_LT(0, event.concurrent_scope_data[i]);
else
EXPECT_EQ(0, event.concurrent_scope_data[i]);
}
}
}
} // namespace internal
} // namespace cppgc
#endif // CPPGC_IS_STANDALONE

View File

@ -0,0 +1,275 @@
// 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/stats-collector.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace {
constexpr size_t kNoMarkedBytes = 0;
constexpr size_t kMinReportedSize = StatsCollector::kAllocationThresholdBytes;
class StatsCollectorTest : public ::testing::Test {
public:
static constexpr Platform* kNoPlatform = nullptr;
StatsCollectorTest() : stats(kNoPlatform) {}
void FakeAllocate(size_t bytes) {
stats.NotifyAllocation(bytes);
stats.NotifySafePointForConservativeCollection();
}
void FakeFree(size_t bytes) {
stats.NotifyExplicitFree(bytes);
stats.NotifySafePointForConservativeCollection();
}
StatsCollector stats;
};
} // namespace
TEST_F(StatsCollectorTest, NoMarkedBytes) {
stats.NotifyMarkingStarted(CollectionType::kMajor,
GCConfig::MarkingType::kAtomic,
GCConfig::IsForcedGC::kNotForced);
stats.NotifyMarkingCompleted(kNoMarkedBytes);
stats.NotifySweepingCompleted(GCConfig::SweepingType::kAtomic);
auto event = stats.GetPreviousEventForTesting();
EXPECT_EQ(0u, event.marked_bytes);
}
TEST_F(StatsCollectorTest, EventPrevGCMarkedObjectSize) {
stats.NotifyMarkingStarted(CollectionType::kMajor,
GCConfig::MarkingType::kAtomic,
GCConfig::IsForcedGC::kNotForced);
stats.NotifyMarkingCompleted(1024);
stats.NotifySweepingCompleted(GCConfig::SweepingType::kAtomic);
auto event = stats.GetPreviousEventForTesting();
EXPECT_EQ(1024u, event.marked_bytes);
}
TEST_F(StatsCollectorTest, AllocationNoReportBelowAllocationThresholdBytes) {
constexpr size_t kObjectSize = 17;
EXPECT_LT(kObjectSize, StatsCollector::kAllocationThresholdBytes);
FakeAllocate(kObjectSize);
EXPECT_EQ(0u, stats.allocated_object_size());
}
TEST_F(StatsCollectorTest, AlllocationReportAboveAllocationThresholdBytes) {
constexpr size_t kObjectSize = StatsCollector::kAllocationThresholdBytes;
EXPECT_GE(kObjectSize, StatsCollector::kAllocationThresholdBytes);
FakeAllocate(kObjectSize);
EXPECT_EQ(kObjectSize, stats.allocated_object_size());
}
TEST_F(StatsCollectorTest, InitialAllocatedObjectSize) {
stats.NotifyMarkingStarted(CollectionType::kMajor,
GCConfig::MarkingType::kAtomic,
GCConfig::IsForcedGC::kNotForced);
EXPECT_EQ(0u, stats.allocated_object_size());
stats.NotifyMarkingCompleted(kNoMarkedBytes);
EXPECT_EQ(0u, stats.allocated_object_size());
stats.NotifySweepingCompleted(GCConfig::SweepingType::kAtomic);
EXPECT_EQ(0u, stats.allocated_object_size());
}
TEST_F(StatsCollectorTest, AllocatedObjectSize) {
stats.NotifyMarkingStarted(CollectionType::kMajor,
GCConfig::MarkingType::kAtomic,
GCConfig::IsForcedGC::kNotForced);
FakeAllocate(kMinReportedSize);
EXPECT_EQ(kMinReportedSize, stats.allocated_object_size());
stats.NotifyMarkingCompleted(kMinReportedSize);
EXPECT_EQ(kMinReportedSize, stats.allocated_object_size());
stats.NotifySweepingCompleted(GCConfig::SweepingType::kAtomic);
EXPECT_EQ(kMinReportedSize, stats.allocated_object_size());
}
TEST_F(StatsCollectorTest, AllocatedObjectSizeNoMarkedBytes) {
stats.NotifyMarkingStarted(CollectionType::kMajor,
GCConfig::MarkingType::kAtomic,
GCConfig::IsForcedGC::kNotForced);
FakeAllocate(kMinReportedSize);
EXPECT_EQ(kMinReportedSize, stats.allocated_object_size());
stats.NotifyMarkingCompleted(kNoMarkedBytes);
EXPECT_EQ(0u, stats.allocated_object_size());
stats.NotifySweepingCompleted(GCConfig::SweepingType::kAtomic);
EXPECT_EQ(0u, stats.allocated_object_size());
}
TEST_F(StatsCollectorTest, AllocatedObjectSizeAllocateAfterMarking) {
stats.NotifyMarkingStarted(CollectionType::kMajor,
GCConfig::MarkingType::kAtomic,
GCConfig::IsForcedGC::kNotForced);
FakeAllocate(kMinReportedSize);
EXPECT_EQ(kMinReportedSize, stats.allocated_object_size());
stats.NotifyMarkingCompleted(kMinReportedSize);
FakeAllocate(kMinReportedSize);
EXPECT_EQ(2 * kMinReportedSize, stats.allocated_object_size());
stats.NotifySweepingCompleted(GCConfig::SweepingType::kAtomic);
EXPECT_EQ(2 * kMinReportedSize, stats.allocated_object_size());
}
class MockAllocationObserver : public StatsCollector::AllocationObserver {
public:
MOCK_METHOD(void, AllocatedObjectSizeIncreased, (size_t), (override));
MOCK_METHOD(void, AllocatedObjectSizeDecreased, (size_t), (override));
MOCK_METHOD(void, ResetAllocatedObjectSize, (size_t), (override));
MOCK_METHOD(void, AllocatedSizeIncreased, (size_t), (override));
MOCK_METHOD(void, AllocatedSizeDecreased, (size_t), (override));
};
TEST_F(StatsCollectorTest, RegisterUnregisterObserver) {
MockAllocationObserver observer;
stats.RegisterObserver(&observer);
stats.UnregisterObserver(&observer);
}
TEST_F(StatsCollectorTest, ObserveAllocatedObjectSizeIncreaseAndDecrease) {
MockAllocationObserver observer;
stats.RegisterObserver(&observer);
EXPECT_CALL(observer, AllocatedObjectSizeIncreased(kMinReportedSize));
FakeAllocate(kMinReportedSize);
EXPECT_CALL(observer, AllocatedObjectSizeDecreased(kMinReportedSize));
FakeFree(kMinReportedSize);
stats.UnregisterObserver(&observer);
}
namespace {
void FakeGC(StatsCollector* stats, size_t marked_bytes) {
stats->NotifyMarkingStarted(CollectionType::kMajor,
GCConfig::MarkingType::kAtomic,
GCConfig::IsForcedGC::kNotForced);
stats->NotifyMarkingCompleted(marked_bytes);
stats->NotifySweepingCompleted(GCConfig::SweepingType::kAtomic);
}
} // namespace
TEST_F(StatsCollectorTest, ObserveResetAllocatedObjectSize) {
MockAllocationObserver observer;
stats.RegisterObserver(&observer);
EXPECT_CALL(observer, AllocatedObjectSizeIncreased(kMinReportedSize));
FakeAllocate(kMinReportedSize);
EXPECT_CALL(observer, ResetAllocatedObjectSize(64));
FakeGC(&stats, 64);
stats.UnregisterObserver(&observer);
}
TEST_F(StatsCollectorTest, ObserveAllocatedMemoryIncreaseAndDecrease) {
MockAllocationObserver observer;
stats.RegisterObserver(&observer);
static constexpr size_t kAllocatedMemorySize = 4096;
EXPECT_CALL(observer, AllocatedSizeIncreased(kAllocatedMemorySize));
stats.NotifyAllocatedMemory(kAllocatedMemorySize);
static constexpr size_t kFreedMemorySize = 2048;
EXPECT_CALL(observer, AllocatedSizeDecreased(kFreedMemorySize));
stats.NotifyFreedMemory(kFreedMemorySize);
stats.UnregisterObserver(&observer);
}
namespace {
class AllocationObserverTriggeringGC final
: public StatsCollector::AllocationObserver {
public:
AllocationObserverTriggeringGC(StatsCollector* stats, double survival_ratio)
: stats(stats), survival_ratio_(survival_ratio) {}
void AllocatedObjectSizeIncreased(size_t bytes) final {
increase_call_count++;
increased_size_bytes += bytes;
if (increase_call_count == 1) {
FakeGC(stats, bytes * survival_ratio_);
}
}
// // Mock out the rest to trigger warnings if used.
MOCK_METHOD(void, AllocatedObjectSizeDecreased, (size_t), (override));
MOCK_METHOD(void, ResetAllocatedObjectSize, (size_t), (override));
size_t increase_call_count = 0;
size_t increased_size_bytes = 0;
StatsCollector* stats;
double survival_ratio_;
};
} // namespace
TEST_F(StatsCollectorTest, ObserverTriggersGC) {
constexpr double kSurvivalRatio = 0.5;
AllocationObserverTriggeringGC gc_observer(&stats, kSurvivalRatio);
MockAllocationObserver mock_observer;
// Internal detail: First registered observer is also notified first.
stats.RegisterObserver(&gc_observer);
stats.RegisterObserver(&mock_observer);
// Both observers see the exact allocated object size byte count.
EXPECT_CALL(mock_observer,
ResetAllocatedObjectSize(kMinReportedSize * kSurvivalRatio));
EXPECT_CALL(gc_observer,
ResetAllocatedObjectSize(kMinReportedSize * kSurvivalRatio));
// Since the GC clears counters, mock_observer should see an increase call
// with a delta of zero bytes. This expectation makes use of the internal
// detail that first registered observer triggers GC.
EXPECT_CALL(mock_observer, AllocatedObjectSizeIncreased(0));
// Trigger scenario.
FakeAllocate(kMinReportedSize);
EXPECT_EQ(1u, gc_observer.increase_call_count);
EXPECT_EQ(kMinReportedSize, gc_observer.increased_size_bytes);
stats.UnregisterObserver(&gc_observer);
stats.UnregisterObserver(&mock_observer);
}
TEST_F(StatsCollectorTest, AllocatedMemorySize) {
EXPECT_EQ(0u, stats.allocated_memory_size());
stats.NotifyAllocatedMemory(1024);
EXPECT_EQ(1024u, stats.allocated_memory_size());
stats.NotifyFreedMemory(1024);
EXPECT_EQ(0u, stats.allocated_memory_size());
}
TEST_F(StatsCollectorTest, DiscardedMemorySize) {
EXPECT_EQ(0u, stats.discarded_memory_size());
stats.IncrementDiscardedMemory(1024);
EXPECT_EQ(1024u, stats.discarded_memory_size());
stats.DecrementDiscardedMemory(1024);
EXPECT_EQ(0u, stats.discarded_memory_size());
}
TEST_F(StatsCollectorTest, ResidentMemorySizeWithoutDiscarded) {
EXPECT_EQ(0u, stats.resident_memory_size());
stats.NotifyAllocatedMemory(1024);
EXPECT_EQ(1024u, stats.resident_memory_size());
stats.NotifyFreedMemory(1024);
EXPECT_EQ(0u, stats.resident_memory_size());
}
TEST_F(StatsCollectorTest, ResidentMemorySizeWithDiscarded) {
EXPECT_EQ(0u, stats.resident_memory_size());
stats.NotifyAllocatedMemory(8192);
EXPECT_EQ(8192u, stats.resident_memory_size());
stats.IncrementDiscardedMemory(4096);
EXPECT_EQ(4096u, stats.resident_memory_size());
stats.DecrementDiscardedMemory(4096);
EXPECT_EQ(8192u, stats.resident_memory_size());
stats.NotifyFreedMemory(8192);
EXPECT_EQ(0u, stats.resident_memory_size());
}
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,541 @@
// 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/sweeper.h"
#include <algorithm>
#include "include/cppgc/allocation.h"
#include "include/cppgc/cross-thread-persistent.h"
#include "include/cppgc/persistent.h"
#include "src/heap/cppgc/globals.h"
#include "src/heap/cppgc/heap-object-header.h"
#include "src/heap/cppgc/heap-page.h"
#include "src/heap/cppgc/heap-visitor.h"
#include "src/heap/cppgc/heap.h"
#include "src/heap/cppgc/object-view.h"
#include "src/heap/cppgc/page-memory.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 {
size_t g_destructor_callcount;
template <size_t Size>
class GCed : public GarbageCollected<GCed<Size>> {
public:
virtual ~GCed() { ++g_destructor_callcount; }
virtual void Trace(cppgc::Visitor*) const {}
private:
char array[Size];
};
class SweeperTest : public testing::TestWithHeap {
public:
SweeperTest() { g_destructor_callcount = 0; }
void Sweep() {
Heap* heap = Heap::From(GetHeap());
ResetLinearAllocationBuffers();
Sweeper& sweeper = heap->sweeper();
// Pretend do finish marking as StatsCollector verifies that Notify*
// methods are called in the right order.
heap->stats_collector()->NotifyMarkingStarted(
CollectionType::kMajor, GCConfig::MarkingType::kAtomic,
GCConfig::IsForcedGC::kNotForced);
heap->stats_collector()->NotifyMarkingCompleted(0);
const SweepingConfig sweeping_config{
SweepingConfig::SweepingType::kAtomic,
SweepingConfig::CompactableSpaceHandling::kSweep};
sweeper.Start(sweeping_config);
sweeper.FinishIfRunning();
}
void MarkObject(void* payload) {
HeapObjectHeader& header = HeapObjectHeader::FromObject(payload);
header.TryMarkAtomic();
BasePage* page = BasePage::FromPayload(&header);
page->IncrementMarkedBytes(page->is_large()
? LargePage::From(page)->PayloadSize()
: header.AllocatedSize());
}
PageBackend* GetBackend() { return Heap::From(GetHeap())->page_backend(); }
};
} // namespace
TEST_F(SweeperTest, SweepUnmarkedNormalObject) {
constexpr size_t kObjectSize = 8;
using Type = GCed<kObjectSize>;
MakeGarbageCollected<Type>(GetAllocationHandle());
EXPECT_EQ(0u, g_destructor_callcount);
Sweep();
EXPECT_EQ(1u, g_destructor_callcount);
}
TEST_F(SweeperTest, DontSweepMarkedNormalObject) {
constexpr size_t kObjectSize = 8;
using Type = GCed<kObjectSize>;
auto* object = MakeGarbageCollected<Type>(GetAllocationHandle());
MarkObject(object);
BasePage* page = BasePage::FromPayload(object);
BaseSpace& space = page->space();
EXPECT_EQ(0u, g_destructor_callcount);
Sweep();
EXPECT_EQ(0u, g_destructor_callcount);
// Check that page is returned back to the space.
EXPECT_NE(space.end(), std::find(space.begin(), space.end(), page));
EXPECT_NE(nullptr, GetBackend()->Lookup(reinterpret_cast<Address>(object)));
}
TEST_F(SweeperTest, SweepUnmarkedLargeObject) {
constexpr size_t kObjectSize = kLargeObjectSizeThreshold * 2;
using Type = GCed<kObjectSize>;
auto* object = MakeGarbageCollected<Type>(GetAllocationHandle());
BasePage* page = BasePage::FromPayload(object);
BaseSpace& space = page->space();
EXPECT_EQ(0u, g_destructor_callcount);
Sweep();
EXPECT_EQ(1u, g_destructor_callcount);
// Check that page is gone.
EXPECT_EQ(space.end(), std::find(space.begin(), space.end(), page));
EXPECT_EQ(nullptr, GetBackend()->Lookup(reinterpret_cast<Address>(object)));
}
TEST_F(SweeperTest, DontSweepMarkedLargeObject) {
constexpr size_t kObjectSize = kLargeObjectSizeThreshold * 2;
using Type = GCed<kObjectSize>;
auto* object = MakeGarbageCollected<Type>(GetAllocationHandle());
MarkObject(object);
BasePage* page = BasePage::FromPayload(object);
BaseSpace& space = page->space();
EXPECT_EQ(0u, g_destructor_callcount);
Sweep();
EXPECT_EQ(0u, g_destructor_callcount);
// Check that page is returned back to the space.
EXPECT_NE(space.end(), std::find(space.begin(), space.end(), page));
EXPECT_NE(nullptr, GetBackend()->Lookup(reinterpret_cast<Address>(object)));
}
TEST_F(SweeperTest, SweepMultipleObjectsOnPage) {
constexpr size_t kObjectSize = 8;
using Type = GCed<kObjectSize>;
const size_t kNumberOfObjects =
NormalPage::PayloadSize() / (sizeof(Type) + sizeof(HeapObjectHeader));
for (size_t i = 0; i < kNumberOfObjects; ++i) {
MakeGarbageCollected<Type>(GetAllocationHandle());
}
EXPECT_EQ(0u, g_destructor_callcount);
Sweep();
EXPECT_EQ(kNumberOfObjects, g_destructor_callcount);
}
TEST_F(SweeperTest, SweepObjectsOnAllArenas) {
MakeGarbageCollected<GCed<1>>(GetAllocationHandle());
MakeGarbageCollected<GCed<32>>(GetAllocationHandle());
MakeGarbageCollected<GCed<64>>(GetAllocationHandle());
MakeGarbageCollected<GCed<128>>(GetAllocationHandle());
MakeGarbageCollected<GCed<2 * kLargeObjectSizeThreshold>>(
GetAllocationHandle());
EXPECT_EQ(0u, g_destructor_callcount);
Sweep();
EXPECT_EQ(5u, g_destructor_callcount);
}
TEST_F(SweeperTest, SweepMultiplePagesInSingleSpace) {
MakeGarbageCollected<GCed<2 * kLargeObjectSizeThreshold>>(
GetAllocationHandle());
MakeGarbageCollected<GCed<2 * kLargeObjectSizeThreshold>>(
GetAllocationHandle());
MakeGarbageCollected<GCed<2 * kLargeObjectSizeThreshold>>(
GetAllocationHandle());
EXPECT_EQ(0u, g_destructor_callcount);
Sweep();
EXPECT_EQ(3u, g_destructor_callcount);
}
TEST_F(SweeperTest, CoalesceFreeListEntries) {
constexpr size_t kObjectSize = 32;
using Type = GCed<kObjectSize>;
auto* object1 = MakeGarbageCollected<Type>(GetAllocationHandle());
auto* object2 = MakeGarbageCollected<Type>(GetAllocationHandle());
auto* object3 = MakeGarbageCollected<Type>(GetAllocationHandle());
auto* object4 = MakeGarbageCollected<Type>(GetAllocationHandle());
MarkObject(object1);
MarkObject(object4);
Address object2_start =
reinterpret_cast<Address>(&HeapObjectHeader::FromObject(object2));
Address object3_end =
reinterpret_cast<Address>(&HeapObjectHeader::FromObject(object3)) +
HeapObjectHeader::FromObject(object3).AllocatedSize();
const BasePage* page = BasePage::FromPayload(object2);
const FreeList& freelist = NormalPageSpace::From(page->space()).free_list();
const FreeList::Block coalesced_block = {
object2_start, static_cast<size_t>(object3_end - object2_start)};
EXPECT_EQ(0u, g_destructor_callcount);
EXPECT_FALSE(freelist.ContainsForTesting(coalesced_block));
Sweep();
EXPECT_EQ(2u, g_destructor_callcount);
EXPECT_TRUE(freelist.ContainsForTesting(coalesced_block));
}
namespace {
class GCInDestructor final : public GarbageCollected<GCInDestructor> {
public:
explicit GCInDestructor(Heap* heap) : heap_(heap) {}
~GCInDestructor() {
// Instead of directly calling GC, allocations should be supported here as
// well.
heap_->CollectGarbage(internal::GCConfig::ConservativeAtomicConfig());
}
void Trace(Visitor*) const {}
private:
Heap* heap_;
};
} // namespace
TEST_F(SweeperTest, SweepDoesNotTriggerRecursiveGC) {
auto* internal_heap = internal::Heap::From(GetHeap());
size_t saved_epoch = internal_heap->epoch();
MakeGarbageCollected<GCInDestructor>(GetAllocationHandle(), internal_heap);
PreciseGC();
EXPECT_EQ(saved_epoch + 1, internal_heap->epoch());
}
TEST_F(SweeperTest, UnmarkObjects) {
auto* normal_object = MakeGarbageCollected<GCed<32>>(GetAllocationHandle());
auto* large_object =
MakeGarbageCollected<GCed<kLargeObjectSizeThreshold * 2>>(
GetAllocationHandle());
auto& normal_object_header = HeapObjectHeader::FromObject(normal_object);
auto& large_object_header = HeapObjectHeader::FromObject(large_object);
MarkObject(normal_object);
MarkObject(large_object);
EXPECT_TRUE(normal_object_header.IsMarked());
EXPECT_TRUE(large_object_header.IsMarked());
Sweep();
if (Heap::From(GetHeap())->generational_gc_supported()) {
EXPECT_TRUE(normal_object_header.IsMarked());
EXPECT_TRUE(large_object_header.IsMarked());
} else {
EXPECT_FALSE(normal_object_header.IsMarked());
EXPECT_FALSE(large_object_header.IsMarked());
}
}
TEST_F(SweeperTest, LazySweepingDuringAllocation) {
// The test allocates objects in such a way that the object with its header is
// power of two. This is to make sure that if there is some padding at the end
// of the page, it will go to a different freelist bucket. To get that,
// subtract vptr and object-header-size from a power-of-two.
static constexpr size_t kGCObjectSize =
256 - sizeof(void*) - sizeof(HeapObjectHeader);
using GCedObject = GCed<kGCObjectSize>;
static_assert(v8::base::bits::IsPowerOfTwo(sizeof(GCedObject) +
sizeof(HeapObjectHeader)));
static const size_t kObjectsPerPage =
NormalPage::PayloadSize() /
(sizeof(GCedObject) + sizeof(HeapObjectHeader));
// This test expects each page contain at least 2 objects.
DCHECK_LT(2u, kObjectsPerPage);
PreciseGC();
std::vector<Persistent<GCedObject>> first_page;
first_page.push_back(MakeGarbageCollected<GCedObject>(GetAllocationHandle()));
GCedObject* expected_address_on_first_page =
MakeGarbageCollected<GCedObject>(GetAllocationHandle());
for (size_t i = 2; i < kObjectsPerPage; ++i) {
first_page.push_back(
MakeGarbageCollected<GCedObject>(GetAllocationHandle()));
}
std::vector<Persistent<GCedObject>> second_page;
second_page.push_back(
MakeGarbageCollected<GCedObject>(GetAllocationHandle()));
GCedObject* expected_address_on_second_page =
MakeGarbageCollected<GCedObject>(GetAllocationHandle());
for (size_t i = 2; i < kObjectsPerPage; ++i) {
second_page.push_back(
MakeGarbageCollected<GCedObject>(GetAllocationHandle()));
}
testing::TestPlatform::DisableBackgroundTasksScope no_concurrent_sweep_scope(
GetPlatformHandle().get());
g_destructor_callcount = 0;
static constexpr GCConfig config = {
CollectionType::kMajor, StackState::kNoHeapPointers,
GCConfig::MarkingType::kAtomic,
GCConfig::SweepingType::kIncrementalAndConcurrent};
Heap::From(GetHeap())->CollectGarbage(config);
// Incremental sweeping is active and the space should have two pages with
// no room for an additional GCedObject. Allocating a new GCedObject should
// trigger sweeping. All objects other than the 2nd object on each page are
// marked. Lazy sweeping on allocation should reclaim the object on one of
// the pages and reuse its memory. The object on the other page should remain
// un-reclaimed. To confirm: the newly object will be allcoated at one of the
// expected addresses and the GCedObject destructor is only called once.
GCedObject* new_object1 =
MakeGarbageCollected<GCedObject>(GetAllocationHandle());
EXPECT_EQ(1u, g_destructor_callcount);
EXPECT_TRUE((new_object1 == expected_address_on_first_page) ||
(new_object1 == expected_address_on_second_page));
// Allocating again should reclaim the other unmarked object and reuse its
// memory. The destructor will be called again and the new object will be
// allocated in one of the expected addresses but not the same one as before.
GCedObject* new_object2 =
MakeGarbageCollected<GCedObject>(GetAllocationHandle());
EXPECT_EQ(2u, g_destructor_callcount);
EXPECT_TRUE((new_object2 == expected_address_on_first_page) ||
(new_object2 == expected_address_on_second_page));
EXPECT_NE(new_object1, new_object2);
}
TEST_F(SweeperTest, LazySweepingNormalPages) {
using GCedObject = GCed<sizeof(size_t)>;
EXPECT_EQ(0u, g_destructor_callcount);
PreciseGC();
EXPECT_EQ(0u, g_destructor_callcount);
MakeGarbageCollected<GCedObject>(GetAllocationHandle());
static constexpr GCConfig config = {
CollectionType::kMajor, StackState::kNoHeapPointers,
GCConfig::MarkingType::kAtomic,
// Sweeping type must not include concurrent as that could lead to the
// concurrent sweeper holding onto pages in rare cases which delays
// reclamation of objects.
GCConfig::SweepingType::kIncremental};
Heap::From(GetHeap())->CollectGarbage(config);
EXPECT_EQ(0u, g_destructor_callcount);
MakeGarbageCollected<GCedObject>(GetAllocationHandle());
EXPECT_EQ(1u, g_destructor_callcount);
PreciseGC();
EXPECT_EQ(2u, g_destructor_callcount);
}
namespace {
class AllocatingFinalizer : public GarbageCollected<AllocatingFinalizer> {
public:
static size_t destructor_callcount_;
explicit AllocatingFinalizer(AllocationHandle& allocation_handle)
: allocation_handle_(allocation_handle) {}
~AllocatingFinalizer() {
MakeGarbageCollected<GCed<sizeof(size_t)>>(allocation_handle_);
++destructor_callcount_;
}
void Trace(Visitor*) const {}
private:
AllocationHandle& allocation_handle_;
};
size_t AllocatingFinalizer::destructor_callcount_ = 0;
} // namespace
TEST_F(SweeperTest, AllocationDuringFinalizationIsNotSwept) {
AllocatingFinalizer::destructor_callcount_ = 0;
g_destructor_callcount = 0;
MakeGarbageCollected<AllocatingFinalizer>(GetAllocationHandle(),
GetAllocationHandle());
PreciseGC();
EXPECT_LT(0u, AllocatingFinalizer::destructor_callcount_);
EXPECT_EQ(0u, g_destructor_callcount);
}
TEST_F(SweeperTest, DiscardingNormalPageMemory) {
if (!Sweeper::CanDiscardMemory()) return;
// Test ensures that free list payload is discarded and accounted for on page
// level.
auto* holder = MakeGarbageCollected<GCed<1>>(GetAllocationHandle());
ConservativeMemoryDiscardingGC();
auto* page = NormalPage::FromPayload(holder);
// Assume the `holder` object is the first on the page for simplifying exact
// discarded count.
ASSERT_EQ(static_cast<void*>(page->PayloadStart() + sizeof(HeapObjectHeader)),
holder);
// No other object on the page is live.
Address free_list_payload_start =
page->PayloadStart() +
HeapObjectHeader::FromObject(holder).AllocatedSize() +
sizeof(kFreeListEntrySize);
uintptr_t start =
RoundUp(reinterpret_cast<uintptr_t>(free_list_payload_start),
GetPlatform().GetPageAllocator()->CommitPageSize());
uintptr_t end = RoundDown(reinterpret_cast<uintptr_t>(page->PayloadEnd()),
GetPlatform().GetPageAllocator()->CommitPageSize());
EXPECT_GT(end, start);
EXPECT_EQ(page->discarded_memory(), end - start);
USE(holder);
}
namespace {
class Holder final : public GarbageCollected<Holder> {
public:
static size_t destructor_callcount;
void Trace(Visitor*) const {}
~Holder() {
EXPECT_FALSE(ref);
EXPECT_FALSE(weak_ref);
destructor_callcount++;
}
cppgc::subtle::CrossThreadPersistent<GCed<1>> ref;
cppgc::subtle::WeakCrossThreadPersistent<GCed<1>> weak_ref;
};
// static
size_t Holder::destructor_callcount;
} // namespace
TEST_F(SweeperTest, CrossThreadPersistentCanBeClearedFromOtherThread) {
Holder::destructor_callcount = 0;
auto* holder = MakeGarbageCollected<Holder>(GetAllocationHandle());
auto remote_heap = cppgc::Heap::Create(GetPlatformHandle());
// The case below must be able to clear both, the CTP and WCTP.
holder->ref =
MakeGarbageCollected<GCed<1>>(remote_heap->GetAllocationHandle());
holder->weak_ref =
MakeGarbageCollected<GCed<1>>(remote_heap->GetAllocationHandle());
testing::TestPlatform::DisableBackgroundTasksScope no_concurrent_sweep_scope(
GetPlatformHandle().get());
Heap::From(GetHeap())->CollectGarbage(
{CollectionType::kMajor, StackState::kNoHeapPointers,
GCConfig::MarkingType::kAtomic,
GCConfig::SweepingType::kIncrementalAndConcurrent});
// `holder` is unreachable (as the stack is not scanned) and will be
// reclaimed. Its payload memory is generally poisoned at this point. The
// CrossThreadPersistent slot should be unpoisoned.
// Terminate the remote heap which should also clear `holder->ref`. The slot
// for `ref` should have been unpoisoned by the GC.
Heap::From(remote_heap.get())->Terminate();
// Finish the sweeper which will find the CrossThreadPersistent in cleared
// state.
Heap::From(GetHeap())->sweeper().FinishIfRunning();
EXPECT_EQ(1u, Holder::destructor_callcount);
}
TEST_F(SweeperTest, WeakCrossThreadPersistentCanBeClearedFromOtherThread) {
Holder::destructor_callcount = 0;
auto* holder = MakeGarbageCollected<Holder>(GetAllocationHandle());
auto remote_heap = cppgc::Heap::Create(GetPlatformHandle());
holder->weak_ref =
MakeGarbageCollected<GCed<1>>(remote_heap->GetAllocationHandle());
testing::TestPlatform::DisableBackgroundTasksScope no_concurrent_sweep_scope(
GetPlatformHandle().get());
static constexpr GCConfig config = {
CollectionType::kMajor, StackState::kNoHeapPointers,
GCConfig::MarkingType::kAtomic,
GCConfig::SweepingType::kIncrementalAndConcurrent};
Heap::From(GetHeap())->CollectGarbage(config);
// `holder` is unreachable (as the stack is not scanned) and will be
// reclaimed. Its payload memory is generally poisoned at this point. The
// WeakCrossThreadPersistent slot should be unpoisoned during clearing.
// GC in the remote heap should also clear `holder->weak_ref`. The slot for
// `weak_ref` should be unpoisoned by the GC.
Heap::From(remote_heap.get())
->CollectGarbage({CollectionType::kMajor, StackState::kNoHeapPointers,
GCConfig::MarkingType::kAtomic,
GCConfig::SweepingType::kAtomic});
// Finish the sweeper which will find the CrossThreadPersistent in cleared
// state.
Heap::From(GetHeap())->sweeper().FinishIfRunning();
EXPECT_EQ(1u, Holder::destructor_callcount);
}
TEST_F(SweeperTest, SweepOnAllocationTakeLastFreeListEntry) {
// The test allocates the following layout:
// |--object-A--|-object-B-|--object-A--|---free-space---|
// Objects A are reachable, whereas object B is not. sizeof(B) is smaller than
// that of A. The test starts garbage-collection with lazy sweeping, then
// tries to allocate object A, expecting the allocation to end up on the same
// page at the free-space.
using GCedA = GCed<256>;
using GCedB = GCed<240>;
PreciseGC();
// Allocate the layout.
Persistent<GCedA> a1 = MakeGarbageCollected<GCedA>(GetAllocationHandle());
MakeGarbageCollected<GCedB>(GetAllocationHandle());
Persistent<GCedA> a2 = MakeGarbageCollected<GCedA>(GetAllocationHandle());
ConstAddress free_space_start =
ObjectView<>(HeapObjectHeader::FromObject(a2.Get())).End();
// Start the GC without sweeping.
testing::TestPlatform::DisableBackgroundTasksScope no_concurrent_sweep_scope(
GetPlatformHandle().get());
static constexpr GCConfig config = {
CollectionType::kMajor, StackState::kNoHeapPointers,
GCConfig::MarkingType::kAtomic,
GCConfig::SweepingType::kIncrementalAndConcurrent};
Heap::From(GetHeap())->CollectGarbage(config);
// Allocate and sweep.
const GCedA* allocated_after_sweeping =
MakeGarbageCollected<GCedA>(GetAllocationHandle());
EXPECT_EQ(free_space_start,
reinterpret_cast<ConstAddress>(
&HeapObjectHeader::FromObject(allocated_after_sweeping)));
}
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,49 @@
// 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 "test/unittests/heap/cppgc/test-platform.h"
#include "include/libplatform/libplatform.h"
#include "src/base/platform/platform.h"
#include "src/base/platform/time.h"
namespace cppgc {
namespace internal {
namespace testing {
TestPlatform::TestPlatform(
std::unique_ptr<v8::TracingController> tracing_controller)
: DefaultPlatform(0 /* thread_pool_size */, IdleTaskSupport::kEnabled,
std::move(tracing_controller)) {}
std::unique_ptr<cppgc::JobHandle> TestPlatform::PostJob(
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::RunAllForegroundTasks() {
while (v8::platform::PumpMessageLoop(v8_platform_.get(), kNoIsolate)) {
}
if (GetForegroundTaskRunner(TaskPriority::kUserBlocking)
->IdleTasksEnabled()) {
v8::platform::RunIdleTasks(v8_platform_.get(), kNoIsolate,
std::numeric_limits<double>::max());
}
}
TestPlatform::DisableBackgroundTasksScope::DisableBackgroundTasksScope(
TestPlatform* platform)
: platform_(platform) {
++platform_->disabled_background_tasks_;
}
TestPlatform::DisableBackgroundTasksScope::~DisableBackgroundTasksScope()
V8_NOEXCEPT {
--platform_->disabled_background_tasks_;
}
} // namespace testing
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,47 @@
// 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.
#ifndef V8_UNITTESTS_HEAP_CPPGC_TEST_PLATFORM_H_
#define V8_UNITTESTS_HEAP_CPPGC_TEST_PLATFORM_H_
#include "include/cppgc/default-platform.h"
#include "src/base/compiler-specific.h"
namespace cppgc {
namespace internal {
namespace testing {
class TestPlatform : public DefaultPlatform {
public:
class V8_NODISCARD DisableBackgroundTasksScope {
public:
explicit DisableBackgroundTasksScope(TestPlatform*);
~DisableBackgroundTasksScope() V8_NOEXCEPT;
private:
TestPlatform* platform_;
};
TestPlatform(
std::unique_ptr<v8::TracingController> tracing_controller = nullptr);
std::unique_ptr<cppgc::JobHandle> PostJob(
cppgc::TaskPriority priority,
std::unique_ptr<cppgc::JobTask> job_task) final;
void RunAllForegroundTasks();
private:
bool AreBackgroundTasksDisabled() const {
return disabled_background_tasks_ > 0;
}
size_t disabled_background_tasks_ = 0;
};
} // namespace testing
} // namespace internal
} // namespace cppgc
#endif // V8_UNITTESTS_HEAP_CPPGC_TEST_PLATFORM_H_

View File

@ -0,0 +1,64 @@
// 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/testing.h"
#include "include/cppgc/allocation.h"
#include "include/cppgc/garbage-collected.h"
#include "include/cppgc/persistent.h"
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace {
class TestingTest : public testing::TestWithHeap {};
class GCed : public GarbageCollected<GCed> {
public:
void Trace(Visitor*) const {}
};
} // namespace
TEST_F(TestingTest,
OverrideEmbeddertackStateScopeDoesNotOverrideExplicitCalls) {
{
auto* gced = MakeGarbageCollected<GCed>(GetHeap()->GetAllocationHandle());
WeakPersistent<GCed> weak{gced};
internal::Heap::From(GetHeap())->CollectGarbage(
GCConfig::PreciseAtomicConfig());
EXPECT_FALSE(weak);
}
{
auto* gced = MakeGarbageCollected<GCed>(GetHeap()->GetAllocationHandle());
WeakPersistent<GCed> weak{gced};
cppgc::testing::OverrideEmbedderStackStateScope override_stack(
GetHeap()->GetHeapHandle(),
EmbedderStackState::kMayContainHeapPointers);
internal::Heap::From(GetHeap())->CollectGarbage(
GCConfig::PreciseAtomicConfig());
EXPECT_FALSE(weak);
}
{
auto* gced = MakeGarbageCollected<GCed>(GetHeap()->GetAllocationHandle());
WeakPersistent<GCed> weak{gced};
cppgc::testing::OverrideEmbedderStackStateScope override_stack(
GetHeap()->GetHeapHandle(), EmbedderStackState::kNoHeapPointers);
internal::Heap::From(GetHeap())->CollectGarbage(
GCConfig::ConservativeAtomicConfig());
EXPECT_TRUE(weak);
}
}
TEST_F(TestingTest, StandaloneTestingHeap) {
// Perform garbage collection through the StandaloneTestingHeap API.
cppgc::testing::StandaloneTestingHeap heap(GetHeap()->GetHeapHandle());
heap.StartGarbageCollection();
heap.PerformMarkingStep(EmbedderStackState::kNoHeapPointers);
heap.FinalizeGarbageCollection(EmbedderStackState::kNoHeapPointers);
}
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,61 @@
// 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 "test/unittests/heap/cppgc/tests.h"
#include <memory>
#include "src/heap/cppgc/object-allocator.h"
#include "test/unittests/heap/cppgc/test-platform.h"
#if !CPPGC_IS_STANDALONE
#include "include/v8-initialization.h"
#include "src/init/v8.h"
#endif // !CPPGC_IS_STANDALONE
namespace cppgc {
namespace internal {
namespace testing {
// static
std::shared_ptr<TestPlatform> TestWithPlatform::platform_;
// static
void TestWithPlatform::SetUpTestSuite() {
platform_ = std::make_shared<TestPlatform>(
std::make_unique<DelegatingTracingController>());
#if !CPPGC_IS_STANDALONE
// For non-standalone builds, we need to initialize V8's platform so that it
// can be looked-up by trace-event.h.
i::V8::InitializePlatformForTesting(platform_->GetV8Platform());
v8::V8::Initialize();
#endif // !CPPGC_IS_STANDALONE
}
// static
void TestWithPlatform::TearDownTestSuite() {
#if !CPPGC_IS_STANDALONE
v8::V8::Dispose();
v8::V8::DisposePlatform();
#endif // !CPPGC_IS_STANDALONE
platform_.reset();
}
TestWithHeap::TestWithHeap()
: heap_(Heap::Create(platform_)),
allocation_handle_(heap_->GetAllocationHandle()) {}
TestWithHeap::~TestWithHeap() = default;
void TestWithHeap::ResetLinearAllocationBuffers() {
Heap::From(GetHeap())->object_allocator().ResetLinearAllocationBuffers();
}
TestSupportingAllocationOnly::TestSupportingAllocationOnly()
: no_gc_scope_(GetHeap()->GetHeapHandle()) {}
} // namespace testing
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,141 @@
// 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.
#ifndef V8_UNITTESTS_HEAP_CPPGC_TESTS_H_
#define V8_UNITTESTS_HEAP_CPPGC_TESTS_H_
#include "include/cppgc/heap-consistency.h"
#include "include/cppgc/heap.h"
#include "include/cppgc/macros.h"
#include "include/cppgc/platform.h"
#include "src/heap/cppgc/heap.h"
#include "src/heap/cppgc/trace-event.h"
#include "test/unittests/heap/cppgc/test-platform.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace testing {
class DelegatingTracingController : public TracingController {
public:
#if !defined(V8_USE_PERFETTO)
const uint8_t* GetCategoryGroupEnabled(const char* name) override {
static const std::string disabled_by_default_tag =
TRACE_DISABLED_BY_DEFAULT("");
static uint8_t yes = 1;
static uint8_t no = 0;
if (strncmp(name, disabled_by_default_tag.c_str(),
disabled_by_default_tag.length()) == 0) {
return &no;
}
return &yes;
}
uint64_t AddTraceEvent(
char phase, const uint8_t* category_enabled_flag, const char* name,
const char* scope, uint64_t id, uint64_t bind_id, int32_t num_args,
const char** arg_names, const uint8_t* arg_types,
const uint64_t* arg_values,
std::unique_ptr<ConvertableToTraceFormat>* arg_convertables,
unsigned int flags) override {
return tracing_controller_->AddTraceEvent(
phase, category_enabled_flag, name, scope, id, bind_id, num_args,
arg_names, arg_types, arg_values, arg_convertables, flags);
}
#endif // !defined(V8_USE_PERFETTO)
void SetTracingController(
std::unique_ptr<TracingController> tracing_controller_impl) {
tracing_controller_ = std::move(tracing_controller_impl);
}
private:
std::unique_ptr<TracingController> tracing_controller_ =
std::make_unique<TracingController>();
};
class TestWithPlatform : public ::testing::Test {
public:
static void SetUpTestSuite();
static void TearDownTestSuite();
TestPlatform& GetPlatform() const { return *platform_; }
std::shared_ptr<TestPlatform> GetPlatformHandle() const { return platform_; }
void SetTracingController(
std::unique_ptr<TracingController> tracing_controller_impl) {
static_cast<DelegatingTracingController*>(platform_->GetTracingController())
->SetTracingController(std::move(tracing_controller_impl));
}
protected:
static std::shared_ptr<TestPlatform> platform_;
};
class TestWithHeap : public TestWithPlatform {
public:
TestWithHeap();
~TestWithHeap() override;
void PreciseGC() {
heap_->ForceGarbageCollectionSlow(
::testing::UnitTest::GetInstance()->current_test_info()->name(),
"Testing", cppgc::Heap::StackState::kNoHeapPointers);
}
void ConservativeGC() {
heap_->ForceGarbageCollectionSlow(
::testing::UnitTest::GetInstance()->current_test_info()->name(),
"Testing", cppgc::Heap::StackState::kMayContainHeapPointers);
}
// GC that also discards unused memory and thus changes the resident size
// size of the heap and corresponding pages.
void ConservativeMemoryDiscardingGC() {
internal::Heap::From(GetHeap())->CollectGarbage(
{CollectionType::kMajor, Heap::StackState::kMayContainHeapPointers,
cppgc::Heap::MarkingType::kAtomic, cppgc::Heap::SweepingType::kAtomic,
GCConfig::FreeMemoryHandling::kDiscardWherePossible});
}
cppgc::Heap* GetHeap() const { return heap_.get(); }
cppgc::AllocationHandle& GetAllocationHandle() const {
return allocation_handle_;
}
cppgc::HeapHandle& GetHeapHandle() const {
return GetHeap()->GetHeapHandle();
}
std::unique_ptr<MarkerBase>& GetMarkerRef() {
return Heap::From(GetHeap())->GetMarkerRefForTesting();
}
void ResetLinearAllocationBuffers();
private:
std::unique_ptr<cppgc::Heap> heap_;
cppgc::AllocationHandle& allocation_handle_;
};
// Restrictive test fixture that supports allocation but will make sure no
// garbage collection is triggered. This is useful for writing idiomatic
// tests where object are allocated on the managed heap while still avoiding
// far reaching test consequences of full garbage collection calls.
class TestSupportingAllocationOnly : public TestWithHeap {
protected:
TestSupportingAllocationOnly();
private:
CPPGC_STACK_ALLOCATED_IGNORE("permitted for test code")
subtle::NoGarbageCollectionScope no_gc_scope_;
};
} // namespace testing
} // namespace internal
} // namespace cppgc
#endif // V8_UNITTESTS_HEAP_CPPGC_TESTS_H_

View File

@ -0,0 +1,499 @@
// 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/visitor.h"
#include "include/cppgc/allocation.h"
#include "include/cppgc/garbage-collected.h"
#include "include/cppgc/member.h"
#include "include/cppgc/trace-trait.h"
#include "src/base/macros.h"
#include "src/heap/cppgc/heap.h"
#include "src/heap/cppgc/liveness-broker.h"
#include "src/heap/cppgc/object-allocator.h"
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace {
class TraceTraitTest : public testing::TestSupportingAllocationOnly {};
class VisitorTest : public testing::TestSupportingAllocationOnly {};
class GCed : public GarbageCollected<GCed> {
public:
static size_t trace_callcount;
GCed() { trace_callcount = 0; }
virtual void Trace(cppgc::Visitor* visitor) const { trace_callcount++; }
};
size_t GCed::trace_callcount;
class GCedMixin : public GarbageCollectedMixin {
public:
static size_t trace_callcount;
GCedMixin() { trace_callcount = 0; }
virtual void Trace(cppgc::Visitor* visitor) const { trace_callcount++; }
};
size_t GCedMixin::trace_callcount;
class OtherPayload {
public:
virtual void* GetDummy() const { return nullptr; }
};
class GCedMixinApplication : public GCed,
public OtherPayload,
public GCedMixin {
public:
void Trace(cppgc::Visitor* visitor) const override {
GCed::Trace(visitor);
GCedMixin::Trace(visitor);
}
};
} // namespace
TEST_F(TraceTraitTest, GetObjectStartGCed) {
auto* gced = MakeGarbageCollected<GCed>(GetAllocationHandle());
EXPECT_EQ(gced,
TraceTrait<GCed>::GetTraceDescriptor(gced).base_object_payload);
}
TEST_F(TraceTraitTest, GetObjectStartGCedMixin) {
auto* gced_mixin_app =
MakeGarbageCollected<GCedMixinApplication>(GetAllocationHandle());
auto* gced_mixin = static_cast<GCedMixin*>(gced_mixin_app);
EXPECT_EQ(gced_mixin_app,
TraceTrait<GCedMixin>::GetTraceDescriptor(gced_mixin)
.base_object_payload);
}
TEST_F(TraceTraitTest, TraceGCed) {
auto* gced = MakeGarbageCollected<GCed>(GetAllocationHandle());
EXPECT_EQ(0u, GCed::trace_callcount);
TraceTrait<GCed>::Trace(nullptr, gced);
EXPECT_EQ(1u, GCed::trace_callcount);
}
TEST_F(TraceTraitTest, TraceGCedMixin) {
auto* gced_mixin_app =
MakeGarbageCollected<GCedMixinApplication>(GetAllocationHandle());
auto* gced_mixin = static_cast<GCedMixin*>(gced_mixin_app);
EXPECT_EQ(0u, GCed::trace_callcount);
TraceTrait<GCedMixin>::Trace(nullptr, gced_mixin);
EXPECT_EQ(1u, GCed::trace_callcount);
}
TEST_F(TraceTraitTest, TraceGCedThroughTraceDescriptor) {
auto* gced = MakeGarbageCollected<GCed>(GetAllocationHandle());
EXPECT_EQ(0u, GCed::trace_callcount);
TraceDescriptor desc = TraceTrait<GCed>::GetTraceDescriptor(gced);
desc.callback(nullptr, desc.base_object_payload);
EXPECT_EQ(1u, GCed::trace_callcount);
}
TEST_F(TraceTraitTest, TraceGCedMixinThroughTraceDescriptor) {
auto* gced_mixin_app =
MakeGarbageCollected<GCedMixinApplication>(GetAllocationHandle());
auto* gced_mixin = static_cast<GCedMixin*>(gced_mixin_app);
EXPECT_EQ(0u, GCed::trace_callcount);
TraceDescriptor desc = TraceTrait<GCedMixin>::GetTraceDescriptor(gced_mixin);
desc.callback(nullptr, desc.base_object_payload);
EXPECT_EQ(1u, GCed::trace_callcount);
}
namespace {
class MixinInstanceWithoutTrace
: public GarbageCollected<MixinInstanceWithoutTrace>,
public GCedMixin {};
} // namespace
TEST_F(TraceTraitTest, MixinInstanceWithoutTrace) {
// Verify that a mixin instance without any traceable
// references inherits the mixin's trace implementation.
auto* mixin_without_trace =
MakeGarbageCollected<MixinInstanceWithoutTrace>(GetAllocationHandle());
auto* mixin = static_cast<GCedMixin*>(mixin_without_trace);
EXPECT_EQ(0u, GCedMixin::trace_callcount);
TraceDescriptor mixin_without_trace_desc =
TraceTrait<MixinInstanceWithoutTrace>::GetTraceDescriptor(
mixin_without_trace);
TraceDescriptor mixin_desc = TraceTrait<GCedMixin>::GetTraceDescriptor(mixin);
EXPECT_EQ(mixin_without_trace_desc.callback, mixin_desc.callback);
EXPECT_EQ(mixin_without_trace_desc.base_object_payload,
mixin_desc.base_object_payload);
TraceDescriptor desc =
TraceTrait<MixinInstanceWithoutTrace>::GetTraceDescriptor(
mixin_without_trace);
desc.callback(nullptr, desc.base_object_payload);
EXPECT_EQ(1u, GCedMixin::trace_callcount);
}
namespace {
class DispatchingVisitor : public VisitorBase {
public:
~DispatchingVisitor() override = default;
template <typename T>
void TraceForTesting(T* t) {
TraceRawForTesting(this, t);
}
protected:
void Visit(const void* t, TraceDescriptor desc) override {
desc.callback(this, desc.base_object_payload);
}
};
class CheckingVisitor final : public DispatchingVisitor {
public:
explicit CheckingVisitor(const void* object)
: object_(object), payload_(object) {}
CheckingVisitor(const void* object, const void* payload)
: object_(object), payload_(payload) {}
protected:
void Visit(const void* t, TraceDescriptor desc) final {
EXPECT_EQ(object_, t);
EXPECT_EQ(payload_, desc.base_object_payload);
desc.callback(this, desc.base_object_payload);
}
void VisitWeak(const void* t, TraceDescriptor desc, WeakCallback callback,
const void* weak_member) final {
EXPECT_EQ(object_, t);
EXPECT_EQ(payload_, desc.base_object_payload);
LivenessBroker broker = LivenessBrokerFactory::Create();
callback(broker, weak_member);
}
private:
const void* object_;
const void* payload_;
};
} // namespace
TEST_F(VisitorTest, DispatchTraceGCed) {
auto* gced = MakeGarbageCollected<GCed>(GetAllocationHandle());
CheckingVisitor visitor(gced);
EXPECT_EQ(0u, GCed::trace_callcount);
visitor.TraceForTesting(gced);
EXPECT_EQ(1u, GCed::trace_callcount);
}
TEST_F(VisitorTest, DispatchTraceGCedMixin) {
auto* gced_mixin_app =
MakeGarbageCollected<GCedMixinApplication>(GetAllocationHandle());
auto* gced_mixin = static_cast<GCedMixin*>(gced_mixin_app);
// Ensure that we indeed test dispatching an inner object.
EXPECT_NE(static_cast<void*>(gced_mixin_app), static_cast<void*>(gced_mixin));
CheckingVisitor visitor(gced_mixin, gced_mixin_app);
EXPECT_EQ(0u, GCed::trace_callcount);
visitor.TraceForTesting(gced_mixin);
EXPECT_EQ(1u, GCed::trace_callcount);
}
TEST_F(VisitorTest, DispatchTraceWeakGCed) {
WeakMember<GCed> ref = MakeGarbageCollected<GCed>(GetAllocationHandle());
CheckingVisitor visitor(ref, ref);
visitor.Trace(ref);
// No marking, so reference should be cleared.
EXPECT_EQ(nullptr, ref.Get());
}
TEST_F(VisitorTest, DispatchTraceWeakGCedMixin) {
auto* gced_mixin_app =
MakeGarbageCollected<GCedMixinApplication>(GetAllocationHandle());
auto* gced_mixin = static_cast<GCedMixin*>(gced_mixin_app);
// Ensure that we indeed test dispatching an inner object.
EXPECT_NE(static_cast<void*>(gced_mixin_app), static_cast<void*>(gced_mixin));
WeakMember<GCedMixin> ref = gced_mixin;
CheckingVisitor visitor(gced_mixin, gced_mixin_app);
visitor.Trace(ref);
// No marking, so reference should be cleared.
EXPECT_EQ(nullptr, ref.Get());
}
namespace {
class WeakCallbackVisitor final : public VisitorBase {
public:
void RegisterWeakCallback(WeakCallback callback, const void* param) final {
LivenessBroker broker = LivenessBrokerFactory::Create();
callback(broker, param);
}
};
struct WeakCallbackDispatcher {
static size_t callback_callcount;
static const void* callback_param;
static void Setup(const void* expected_param) {
callback_callcount = 0;
callback_param = expected_param;
}
static void Call(const LivenessBroker& broker, const void* param) {
EXPECT_EQ(callback_param, param);
callback_callcount++;
}
};
size_t WeakCallbackDispatcher::callback_callcount;
const void* WeakCallbackDispatcher::callback_param;
class GCedWithCustomWeakCallback final
: public GarbageCollected<GCedWithCustomWeakCallback> {
public:
void CustomWeakCallbackMethod(const LivenessBroker& broker) {
WeakCallbackDispatcher::Call(broker, this);
}
void Trace(cppgc::Visitor* visitor) const {
visitor->RegisterWeakCallbackMethod<
GCedWithCustomWeakCallback,
&GCedWithCustomWeakCallback::CustomWeakCallbackMethod>(this);
}
};
} // namespace
TEST_F(VisitorTest, DispatchRegisterWeakCallback) {
WeakCallbackVisitor visitor;
WeakCallbackDispatcher::Setup(&visitor);
EXPECT_EQ(0u, WeakCallbackDispatcher::callback_callcount);
visitor.RegisterWeakCallback(WeakCallbackDispatcher::Call, &visitor);
EXPECT_EQ(1u, WeakCallbackDispatcher::callback_callcount);
}
TEST_F(VisitorTest, DispatchRegisterWeakCallbackMethod) {
WeakCallbackVisitor visitor;
auto* gced =
MakeGarbageCollected<GCedWithCustomWeakCallback>(GetAllocationHandle());
WeakCallbackDispatcher::Setup(gced);
EXPECT_EQ(0u, WeakCallbackDispatcher::callback_callcount);
gced->Trace(&visitor);
EXPECT_EQ(1u, WeakCallbackDispatcher::callback_callcount);
}
namespace {
class Composite final {
public:
static size_t callback_callcount;
static constexpr size_t kExpectedTraceCount = 1;
static size_t TraceCount() { return callback_callcount; }
Composite() { callback_callcount = 0; }
void Trace(Visitor* visitor) const { callback_callcount++; }
};
size_t Composite::callback_callcount;
class GCedWithComposite final : public GarbageCollected<GCedWithComposite> {
public:
static constexpr size_t kExpectedTraceCount = Composite::kExpectedTraceCount;
static size_t TraceCount() { return Composite::TraceCount(); }
void Trace(Visitor* visitor) const { visitor->Trace(composite); }
Composite composite;
};
class VirtualBase {
public:
virtual ~VirtualBase() = default;
virtual size_t GetCallbackCount() const = 0;
};
class CompositeWithVtable : public VirtualBase {
public:
static size_t callback_callcount;
static constexpr size_t kExpectedTraceCount = 1;
static size_t TraceCount() { return callback_callcount; }
CompositeWithVtable() { callback_callcount = 0; }
~CompositeWithVtable() override = default;
void Trace(Visitor* visitor) const { callback_callcount++; }
size_t GetCallbackCount() const override { return callback_callcount; }
};
size_t CompositeWithVtable::callback_callcount;
class GCedWithCompositeWithVtable final
: public GarbageCollected<GCedWithCompositeWithVtable> {
public:
static constexpr size_t kExpectedTraceCount = 1;
static size_t TraceCount() { return CompositeWithVtable::callback_callcount; }
void Trace(Visitor* visitor) const { visitor->Trace(composite); }
CompositeWithVtable composite;
};
} // namespace
TEST_F(VisitorTest, DispatchToCompositeObject) {
auto* gced = MakeGarbageCollected<GCedWithComposite>(GetAllocationHandle());
CheckingVisitor visitor(gced);
EXPECT_EQ(0u, GCedWithComposite::TraceCount());
visitor.TraceForTesting(gced);
EXPECT_EQ(GCedWithComposite::kExpectedTraceCount,
GCedWithComposite::TraceCount());
}
TEST_F(VisitorTest, DispatchToCompositeObjectWithVtable) {
auto* gced =
MakeGarbageCollected<GCedWithCompositeWithVtable>(GetAllocationHandle());
CheckingVisitor visitor(gced);
EXPECT_EQ(0u, GCedWithCompositeWithVtable::TraceCount());
visitor.TraceForTesting(gced);
EXPECT_EQ(GCedWithCompositeWithVtable::kExpectedTraceCount,
GCedWithCompositeWithVtable::TraceCount());
}
namespace {
// Fibonacci hashing. See boost::hash_combine.
inline void hash_combine(std::size_t& seed) {}
template <typename T, typename... Rest>
void hash_combine(size_t& seed, const T& v, Rest... rest) {
std::hash<T> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
hash_combine(seed, rest...);
}
class HashingVisitor final : public DispatchingVisitor {
public:
size_t hash() const { return hash_; }
protected:
void Visit(const void* t, TraceDescriptor desc) final {
hash_combine(hash_, desc.base_object_payload);
desc.callback(this, desc.base_object_payload);
}
private:
size_t hash_ = 0;
};
template <template <class> class MemberType, typename GCType>
class GCedWithMultipleMember final
: public GarbageCollected<GCedWithMultipleMember<MemberType, GCType>> {
public:
static constexpr size_t kNumElements = 17;
static constexpr size_t kExpectedTraceCount = kNumElements;
static size_t TraceCount() { return GCType::TraceCount(); }
void Trace(Visitor* visitor) const {
visitor->TraceMultiple(fields, kNumElements);
}
MemberType<GCType> fields[kNumElements];
};
template <class GCType>
void DispatchMultipleMemberTest(AllocationHandle& handle) {
size_t hash = 0;
auto* holder = MakeGarbageCollected<GCType>(handle);
hash_combine(hash, holder);
for (auto i = 0u; i < GCType::kNumElements; ++i) {
holder->fields[i] = MakeGarbageCollected<GCedWithComposite>(handle);
hash_combine(hash, holder->fields[i].Get());
}
HashingVisitor visitor;
EXPECT_EQ(0u, GCType::TraceCount());
visitor.TraceForTesting(holder);
EXPECT_EQ(GCType::kExpectedTraceCount, GCType::TraceCount());
EXPECT_NE(0u, hash);
EXPECT_EQ(hash, visitor.hash());
}
} // namespace
TEST_F(VisitorTest, DispatchToMultipleMember) {
using GCType = GCedWithMultipleMember<Member, GCedWithComposite>;
DispatchMultipleMemberTest<GCType>(GetAllocationHandle());
}
TEST_F(VisitorTest, DispatchToMultipleUncompressedMember) {
using GCType =
GCedWithMultipleMember<subtle::UncompressedMember, GCedWithComposite>;
DispatchMultipleMemberTest<GCType>(GetAllocationHandle());
}
namespace {
class GCedWithMultipleComposite final
: public GarbageCollected<GCedWithMultipleComposite> {
public:
static constexpr size_t kNumElements = 17;
static constexpr size_t kExpectedTraceCount =
kNumElements * Composite::kExpectedTraceCount;
static size_t TraceCount() { return Composite::TraceCount(); }
void Trace(Visitor* visitor) const {
visitor->TraceMultiple(fields, kNumElements);
}
Composite fields[kNumElements];
};
class GCedWithMultipleCompositeUninitializedVtable final
: public GarbageCollected<GCedWithMultipleCompositeUninitializedVtable> {
public:
static constexpr size_t kNumElements = 17;
static constexpr size_t kExpectedTraceCount =
kNumElements * CompositeWithVtable::kExpectedTraceCount;
static size_t TraceCount() { return CompositeWithVtable::TraceCount(); }
explicit GCedWithMultipleCompositeUninitializedVtable(
size_t initialized_fields) {
// Clear some vtable pointers. Such objects should not be traced.
memset(static_cast<void*>(&fields[initialized_fields]), 0,
sizeof(CompositeWithVtable) * (kNumElements - initialized_fields));
}
void Trace(Visitor* visitor) const {
visitor->TraceMultiple(fields, kNumElements);
}
CompositeWithVtable fields[kNumElements];
};
} // namespace
TEST_F(VisitorTest, DispatchToMultipleCompositeObjects) {
auto* holder =
MakeGarbageCollected<GCedWithMultipleComposite>(GetAllocationHandle());
DispatchingVisitor visitor;
EXPECT_EQ(0u, GCedWithMultipleComposite::TraceCount());
visitor.TraceForTesting(holder);
EXPECT_EQ(GCedWithMultipleComposite::kExpectedTraceCount,
GCedWithMultipleComposite::TraceCount());
}
TEST_F(VisitorTest, DispatchMultipleInlinedObjectsWithClearedVtable) {
auto* holder =
MakeGarbageCollected<GCedWithMultipleCompositeUninitializedVtable>(
GetAllocationHandle(), GCedWithMultipleComposite::kNumElements / 2);
DispatchingVisitor visitor;
EXPECT_EQ(0u, GCedWithMultipleCompositeUninitializedVtable::TraceCount());
visitor.TraceForTesting(holder);
EXPECT_EQ(
GCedWithMultipleCompositeUninitializedVtable::kExpectedTraceCount / 2,
GCedWithMultipleCompositeUninitializedVtable::TraceCount());
}
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,190 @@
// 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 <atomic>
#include "include/cppgc/allocation.h"
#include "src/base/macros.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:
void StartMarking() {
CHECK_EQ(0u,
Heap::From(GetHeap())->AsBase().stats_collector()->marked_bytes());
MarkingConfig config = {CollectionType::kMajor, StackState::kNoHeapPointers,
MarkingConfig::MarkingType::kIncremental};
GetMarkerRef() = std::make_unique<Marker>(
Heap::From(GetHeap())->AsBase(), GetPlatformHandle().get(), config);
GetMarkerRef()->StartMarking();
}
void FinishMarking(StackState stack_state) {
GetMarkerRef()->FinishMarking(stack_state);
marked_bytes_ =
Heap::From(GetHeap())->AsBase().stats_collector()->marked_bytes();
GetMarkerRef().reset();
Heap::From(GetHeap())->stats_collector()->NotifySweepingCompleted(
GCConfig::SweepingType::kAtomic);
}
size_t GetMarkedBytes() const { return marked_bytes_; }
private:
size_t marked_bytes_ = 0;
};
template <typename T>
constexpr size_t SizeOf() {
return RoundUp<kAllocationGranularity>(sizeof(T) + sizeof(HeapObjectHeader));
}
class TraceableGCed : public GarbageCollected<TraceableGCed> {
public:
void Trace(cppgc::Visitor*) const {
reinterpret_cast<std::atomic<size_t>*>(&n_trace_calls)
->fetch_add(1, std::memory_order_relaxed);
}
mutable size_t n_trace_calls = 0;
};
class NonTraceableGCed : public GarbageCollected<NonTraceableGCed> {
public:
void Trace(cppgc::Visitor*) const { n_trace_calls++; }
mutable size_t n_trace_calls = 0;
};
void EmptyWeakCallback(const LivenessBroker&, const void*) {}
} // 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());
obj->n_trace_calls = 0u;
StartMarking();
GetMarkerRef()->Visitor().TraceWeakContainer(obj, EmptyWeakCallback, nullptr);
FinishMarking(StackState::kNoHeapPointers);
EXPECT_NE(0u, obj->n_trace_calls);
EXPECT_EQ(SizeOf<TraceableGCed>(), GetMarkedBytes());
}
TEST_F(WeakContainerTest, NonTraceableGCedNotTraced) {
NonTraceableGCed* obj =
MakeGarbageCollected<NonTraceableGCed>(GetAllocationHandle());
obj->n_trace_calls = 0u;
StartMarking();
GetMarkerRef()->Visitor().TraceWeakContainer(obj, EmptyWeakCallback, nullptr);
FinishMarking(StackState::kNoHeapPointers);
EXPECT_EQ(0u, obj->n_trace_calls);
EXPECT_EQ(SizeOf<NonTraceableGCed>(), GetMarkedBytes());
}
TEST_F(WeakContainerTest, NonTraceableGCedNotTracedConservatively) {
NonTraceableGCed* obj =
MakeGarbageCollected<NonTraceableGCed>(GetAllocationHandle());
obj->n_trace_calls = 0u;
StartMarking();
GetMarkerRef()->Visitor().TraceWeakContainer(obj, EmptyWeakCallback, nullptr);
FinishMarking(StackState::kMayContainHeapPointers);
EXPECT_NE(0u, obj->n_trace_calls);
EXPECT_EQ(SizeOf<NonTraceableGCed>(), GetMarkedBytes());
}
TEST_F(WeakContainerTest, PreciseGCTracesWeakContainerWhenTraced) {
TraceableGCed* obj =
MakeGarbageCollected<TraceableGCed>(GetAllocationHandle());
obj->n_trace_calls = 0u;
StartMarking();
GetMarkerRef()->Visitor().TraceWeakContainer(obj, EmptyWeakCallback, nullptr);
FinishMarking(StackState::kNoHeapPointers);
EXPECT_EQ(1u, obj->n_trace_calls);
EXPECT_EQ(SizeOf<TraceableGCed>(), GetMarkedBytes());
}
TEST_F(WeakContainerTest, ConservativeGCTracesWeakContainer) {
TraceableGCed* obj =
MakeGarbageCollected<TraceableGCed>(GetAllocationHandle());
obj->n_trace_calls = 0u;
StartMarking();
GetMarkerRef()->Visitor().TraceWeakContainer(obj, EmptyWeakCallback, nullptr);
FinishMarking(StackState::kMayContainHeapPointers);
EXPECT_EQ(2u, obj->n_trace_calls);
EXPECT_EQ(SizeOf<TraceableGCed>(), GetMarkedBytes());
}
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);
obj->n_trace_calls = 0u;
StartMarking();
GetMarkerRef()->Visitor().TraceWeakContainer(obj, EmptyWeakCallback, nullptr);
FinishMarking(StackState::kMayContainHeapPointers);
EXPECT_EQ(1u, obj->n_trace_calls);
EXPECT_EQ(SizeOf<NonTraceableGCed>(), GetMarkedBytes());
}
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()->Visitor().TraceWeakContainer(obj, WeakCallback::callback,
obj);
FinishMarking(StackState::kMayContainHeapPointers);
EXPECT_NE(0u, WeakCallback::n_callback_called);
EXPECT_EQ(SizeOf<TraceableGCed>(), GetMarkedBytes());
EXPECT_EQ(obj, WeakCallback::obj);
}
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,351 @@
// 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 <algorithm>
#include <iterator>
#include <numeric>
#include "include/cppgc/allocation.h"
#include "include/cppgc/heap-consistency.h"
#include "include/cppgc/persistent.h"
#include "include/cppgc/prefinalizer.h"
#include "src/heap/cppgc/globals.h"
#include "src/heap/cppgc/heap-visitor.h"
#include "src/heap/cppgc/heap.h"
#include "src/heap/cppgc/object-view.h"
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace {
class WorkloadsTest : public testing::TestWithHeap {
public:
void ConservativeGC() {
internal::Heap::From(GetHeap())->CollectGarbage(
GCConfig::ConservativeAtomicConfig());
}
void PreciseGC() {
internal::Heap::From(GetHeap())->CollectGarbage(
GCConfig::PreciseAtomicConfig());
}
};
class SuperClass;
class PointsBack final : public GarbageCollected<PointsBack> {
public:
PointsBack() { ++alive_count_; }
~PointsBack() { --alive_count_; }
void SetBackPointer(SuperClass* back_pointer) {
back_pointer_ = back_pointer;
}
SuperClass* BackPointer() const { return back_pointer_; }
void Trace(Visitor* visitor) const { visitor->Trace(back_pointer_); }
static int alive_count_;
private:
WeakMember<SuperClass> back_pointer_;
};
int PointsBack::alive_count_ = 0;
class SuperClass : public GarbageCollected<SuperClass> {
public:
explicit SuperClass(PointsBack* points_back) : points_back_(points_back) {
points_back_->SetBackPointer(this);
++alive_count_;
}
virtual ~SuperClass() { --alive_count_; }
void InvokeConservativeGCAndExpect(WorkloadsTest* test, SuperClass* target,
PointsBack* points_back,
int super_class_count) {
test->ConservativeGC();
EXPECT_EQ(points_back, target->GetPointsBack());
EXPECT_EQ(super_class_count, SuperClass::alive_count_);
}
virtual void Trace(Visitor* visitor) const { visitor->Trace(points_back_); }
PointsBack* GetPointsBack() const { return points_back_.Get(); }
static int alive_count_;
private:
Member<PointsBack> points_back_;
};
int SuperClass::alive_count_ = 0;
class SubData final : public GarbageCollected<SubData> {
public:
SubData() { ++alive_count_; }
~SubData() { --alive_count_; }
void Trace(Visitor* visitor) const {}
static int alive_count_;
};
int SubData::alive_count_ = 0;
class SubClass final : public SuperClass {
public:
explicit SubClass(AllocationHandle& allocation_handle,
PointsBack* points_back)
: SuperClass(points_back),
data_(MakeGarbageCollected<SubData>(allocation_handle)) {
++alive_count_;
}
~SubClass() final { --alive_count_; }
void Trace(Visitor* visitor) const final {
visitor->Trace(data_);
SuperClass::Trace(visitor);
}
static int alive_count_;
private:
Member<SubData> data_;
};
int SubClass::alive_count_ = 0;
} // namespace
TEST_F(WorkloadsTest, Transition) {
PointsBack::alive_count_ = 0;
SuperClass::alive_count_ = 0;
SubClass::alive_count_ = 0;
SubData::alive_count_ = 0;
Persistent<PointsBack> points_back1 =
MakeGarbageCollected<PointsBack>(GetAllocationHandle());
Persistent<PointsBack> points_back2 =
MakeGarbageCollected<PointsBack>(GetAllocationHandle());
Persistent<SuperClass> super_class =
MakeGarbageCollected<SuperClass>(GetAllocationHandle(), points_back1);
Persistent<SubClass> sub_class = MakeGarbageCollected<SubClass>(
GetAllocationHandle(), GetAllocationHandle(), points_back2);
EXPECT_EQ(2, PointsBack::alive_count_);
EXPECT_EQ(2, SuperClass::alive_count_);
EXPECT_EQ(1, SubClass::alive_count_);
EXPECT_EQ(1, SubData::alive_count_);
PreciseGC();
EXPECT_EQ(2, PointsBack::alive_count_);
EXPECT_EQ(2, SuperClass::alive_count_);
EXPECT_EQ(1, SubClass::alive_count_);
EXPECT_EQ(1, SubData::alive_count_);
super_class->InvokeConservativeGCAndExpect(this, super_class.Release(),
points_back1.Get(), 2);
PreciseGC();
EXPECT_EQ(2, PointsBack::alive_count_);
EXPECT_EQ(1, SuperClass::alive_count_);
EXPECT_EQ(1, SubClass::alive_count_);
EXPECT_EQ(1, SubData::alive_count_);
EXPECT_EQ(nullptr, points_back1->BackPointer());
points_back1.Release();
PreciseGC();
EXPECT_EQ(1, PointsBack::alive_count_);
EXPECT_EQ(1, SuperClass::alive_count_);
EXPECT_EQ(1, SubClass::alive_count_);
EXPECT_EQ(1, SubData::alive_count_);
sub_class->InvokeConservativeGCAndExpect(this, sub_class.Release(),
points_back2.Get(), 1);
PreciseGC();
EXPECT_EQ(1, PointsBack::alive_count_);
EXPECT_EQ(0, SuperClass::alive_count_);
EXPECT_EQ(0, SubClass::alive_count_);
EXPECT_EQ(0, SubData::alive_count_);
EXPECT_EQ(nullptr, points_back2->BackPointer());
points_back2.Release();
PreciseGC();
EXPECT_EQ(0, PointsBack::alive_count_);
EXPECT_EQ(0, SuperClass::alive_count_);
EXPECT_EQ(0, SubClass::alive_count_);
EXPECT_EQ(0, SubData::alive_count_);
EXPECT_EQ(super_class, sub_class);
}
namespace {
class DynamicallySizedObject final
: public GarbageCollected<DynamicallySizedObject> {
public:
static DynamicallySizedObject* Create(AllocationHandle& allocation_handle,
size_t size) {
CHECK_GT(size, sizeof(DynamicallySizedObject));
return MakeGarbageCollected<DynamicallySizedObject>(
allocation_handle,
AdditionalBytes(size - sizeof(DynamicallySizedObject)));
}
uint8_t Get(int i) { return *(reinterpret_cast<uint8_t*>(this) + i); }
void Trace(Visitor* visitor) const {}
};
class ObjectSizeCounter final : private HeapVisitor<ObjectSizeCounter> {
friend class HeapVisitor<ObjectSizeCounter>;
public:
size_t GetSize(RawHeap& heap) {
Traverse(heap);
return accumulated_size_;
}
private:
static size_t ObjectSize(const HeapObjectHeader& header) {
return ObjectView<>(header).Size();
}
bool VisitHeapObjectHeader(HeapObjectHeader& header) {
if (header.IsFree()) return true;
accumulated_size_ += ObjectSize(header);
return true;
}
size_t accumulated_size_ = 0;
};
} // namespace
TEST_F(WorkloadsTest, BasicFunctionality) {
static_assert(kAllocationGranularity % 4 == 0,
"Allocation granularity is expected to be a multiple of 4");
Heap* heap = internal::Heap::From(GetHeap());
size_t initial_object_payload_size =
ObjectSizeCounter().GetSize(heap->raw_heap());
{
// When the test starts there may already have been leaked some memory
// on the heap, so we establish a base line.
size_t base_level = initial_object_payload_size;
bool test_pages_allocated = !base_level;
if (test_pages_allocated) {
EXPECT_EQ(0ul, heap->stats_collector()->allocated_memory_size());
}
// This allocates objects on the general heap which should add a page of
// memory.
DynamicallySizedObject* alloc32 =
DynamicallySizedObject::Create(GetAllocationHandle(), 32);
memset(alloc32, 40, 32);
DynamicallySizedObject* alloc64 =
DynamicallySizedObject::Create(GetAllocationHandle(), 64);
memset(alloc64, 27, 64);
size_t total = 96;
EXPECT_EQ(base_level + total,
ObjectSizeCounter().GetSize(heap->raw_heap()));
if (test_pages_allocated) {
EXPECT_EQ(kPageSize * 2,
heap->stats_collector()->allocated_memory_size());
}
EXPECT_EQ(alloc32->Get(0), 40);
EXPECT_EQ(alloc32->Get(31), 40);
EXPECT_EQ(alloc64->Get(0), 27);
EXPECT_EQ(alloc64->Get(63), 27);
ConservativeGC();
EXPECT_EQ(alloc32->Get(0), 40);
EXPECT_EQ(alloc32->Get(31), 40);
EXPECT_EQ(alloc64->Get(0), 27);
EXPECT_EQ(alloc64->Get(63), 27);
}
PreciseGC();
size_t total = 0;
size_t base_level = ObjectSizeCounter().GetSize(heap->raw_heap());
bool test_pages_allocated = !base_level;
if (test_pages_allocated) {
EXPECT_EQ(0ul, heap->stats_collector()->allocated_memory_size());
}
size_t big = 1008;
Persistent<DynamicallySizedObject> big_area =
DynamicallySizedObject::Create(GetAllocationHandle(), big);
total += big;
size_t persistent_count = 0;
const size_t kNumPersistents = 100000;
Persistent<DynamicallySizedObject>* persistents[kNumPersistents];
for (int i = 0; i < 1000; i++) {
size_t size = 128 + i * 8;
total += size;
persistents[persistent_count++] = new Persistent<DynamicallySizedObject>(
DynamicallySizedObject::Create(GetAllocationHandle(), size));
// The allocations in the loop may trigger GC with lazy sweeping.
heap->sweeper().FinishIfRunning();
EXPECT_EQ(base_level + total,
ObjectSizeCounter().GetSize(heap->raw_heap()));
if (test_pages_allocated) {
EXPECT_EQ(0ul, heap->stats_collector()->allocated_memory_size() &
(kPageSize - 1));
}
}
{
DynamicallySizedObject* alloc32b(
DynamicallySizedObject::Create(GetAllocationHandle(), 32));
memset(alloc32b, 40, 32);
DynamicallySizedObject* alloc64b(
DynamicallySizedObject::Create(GetAllocationHandle(), 64));
memset(alloc64b, 27, 64);
EXPECT_TRUE(alloc32b != alloc64b);
total += 96;
EXPECT_EQ(base_level + total,
ObjectSizeCounter().GetSize(heap->raw_heap()));
if (test_pages_allocated) {
EXPECT_EQ(0ul, heap->stats_collector()->allocated_memory_size() &
(kPageSize - 1));
}
}
PreciseGC();
total -= 96;
if (test_pages_allocated) {
EXPECT_EQ(0ul, heap->stats_collector()->allocated_memory_size() &
(kPageSize - 1));
}
// Clear the persistent, so that the big area will be garbage collected.
big_area.Release();
PreciseGC();
total -= big;
EXPECT_EQ(base_level + total, ObjectSizeCounter().GetSize(heap->raw_heap()));
if (test_pages_allocated) {
EXPECT_EQ(0ul, heap->stats_collector()->allocated_memory_size() &
(kPageSize - 1));
}
EXPECT_EQ(base_level + total, ObjectSizeCounter().GetSize(heap->raw_heap()));
if (test_pages_allocated) {
EXPECT_EQ(0ul, heap->stats_collector()->allocated_memory_size() &
(kPageSize - 1));
}
for (size_t i = 0; i < persistent_count; i++) {
delete persistents[i];
persistents[i] = nullptr;
}
}
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,493 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "include/cppgc/internal/write-barrier.h"
#include <algorithm>
#include <initializer_list>
#include <vector>
#include "include/cppgc/allocation.h"
#include "include/cppgc/heap-consistency.h"
#include "include/cppgc/internal/pointer-policies.h"
#include "include/cppgc/macros.h"
#include "src/base/logging.h"
#include "src/heap/cppgc/heap-object-header.h"
#include "src/heap/cppgc/marker.h"
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace {
class V8_NODISCARD IncrementalMarkingScope {
public:
explicit IncrementalMarkingScope(MarkerBase* marker) : marker_(marker) {}
~IncrementalMarkingScope() V8_NOEXCEPT {
marker_->FinishMarking(kIncrementalConfig.stack_state);
}
static constexpr MarkingConfig kIncrementalConfig{
CollectionType::kMajor, StackState::kNoHeapPointers,
MarkingConfig::MarkingType::kIncremental};
private:
MarkerBase* marker_;
};
constexpr MarkingConfig IncrementalMarkingScope::kIncrementalConfig;
class V8_NODISCARD ExpectWriteBarrierFires final
: private IncrementalMarkingScope {
public:
ExpectWriteBarrierFires(MarkerBase* marker,
std::initializer_list<void*> objects)
: IncrementalMarkingScope(marker),
marking_worklist_(
marker->MutatorMarkingStateForTesting().marking_worklist()),
write_barrier_worklist_(
marker->MutatorMarkingStateForTesting().write_barrier_worklist()),
retrace_marked_objects_worklist_(
marker->MutatorMarkingStateForTesting()
.retrace_marked_objects_worklist()),
objects_(objects) {
EXPECT_TRUE(marking_worklist_.IsGlobalEmpty());
EXPECT_TRUE(write_barrier_worklist_.IsGlobalEmpty());
for (void* object : objects) {
headers_.push_back(&HeapObjectHeader::FromObject(object));
EXPECT_FALSE(headers_.back()->IsMarked());
}
}
~ExpectWriteBarrierFires() V8_NOEXCEPT {
{
MarkingWorklists::MarkingItem item;
while (marking_worklist_.Pop(&item)) {
auto pos = std::find(objects_.begin(), objects_.end(),
item.base_object_payload);
if (pos != objects_.end()) objects_.erase(pos);
}
}
{
HeapObjectHeader* item;
while (write_barrier_worklist_.Pop(&item)) {
auto pos =
std::find(objects_.begin(), objects_.end(), item->ObjectStart());
if (pos != objects_.end()) objects_.erase(pos);
}
}
{
HeapObjectHeader* item;
while (retrace_marked_objects_worklist_.Pop(&item)) {
auto pos =
std::find(objects_.begin(), objects_.end(), item->ObjectStart());
if (pos != objects_.end()) objects_.erase(pos);
}
}
EXPECT_TRUE(objects_.empty());
for (auto* header : headers_) {
EXPECT_TRUE(header->IsMarked());
header->Unmark();
}
EXPECT_TRUE(marking_worklist_.IsGlobalEmpty());
EXPECT_TRUE(write_barrier_worklist_.IsGlobalEmpty());
}
private:
MarkingWorklists::MarkingWorklist::Local& marking_worklist_;
MarkingWorklists::WriteBarrierWorklist::Local& write_barrier_worklist_;
MarkingWorklists::RetraceMarkedObjectsWorklist::Local&
retrace_marked_objects_worklist_;
std::vector<void*> objects_;
std::vector<HeapObjectHeader*> headers_;
};
class V8_NODISCARD ExpectNoWriteBarrierFires final
: private IncrementalMarkingScope {
public:
ExpectNoWriteBarrierFires(MarkerBase* marker,
std::initializer_list<void*> objects)
: IncrementalMarkingScope(marker),
marking_worklist_(
marker->MutatorMarkingStateForTesting().marking_worklist()),
write_barrier_worklist_(
marker->MutatorMarkingStateForTesting().write_barrier_worklist()) {
EXPECT_TRUE(marking_worklist_.IsGlobalEmpty());
EXPECT_TRUE(write_barrier_worklist_.IsGlobalEmpty());
for (void* object : objects) {
auto* header = &HeapObjectHeader::FromObject(object);
headers_.emplace_back(header, header->IsMarked());
}
}
~ExpectNoWriteBarrierFires() {
EXPECT_TRUE(marking_worklist_.IsGlobalEmpty());
EXPECT_TRUE(write_barrier_worklist_.IsGlobalEmpty());
for (const auto& pair : headers_) {
EXPECT_EQ(pair.second, pair.first->IsMarked());
}
}
private:
MarkingWorklists::MarkingWorklist::Local& marking_worklist_;
MarkingWorklists::WriteBarrierWorklist::Local& write_barrier_worklist_;
std::vector<std::pair<HeapObjectHeader*, bool /* was marked */>> headers_;
};
class GCed : public GarbageCollected<GCed> {
public:
GCed() = default;
explicit GCed(GCed* next) : next_(next) {}
void Trace(cppgc::Visitor* v) const { v->Trace(next_); }
bool IsMarked() const {
return HeapObjectHeader::FromObject(this).IsMarked();
}
void set_next(GCed* next) { next_ = next; }
GCed* next() const { return next_; }
Member<GCed>& next_ref() { return next_; }
private:
Member<GCed> next_ = nullptr;
};
} // namespace
class WriteBarrierTest : public testing::TestWithHeap {
public:
WriteBarrierTest() : internal_heap_(Heap::From(GetHeap())) {
DCHECK_NULL(GetMarkerRef().get());
GetMarkerRef() =
std::make_unique<Marker>(*internal_heap_, GetPlatformHandle().get(),
IncrementalMarkingScope::kIncrementalConfig);
marker_ = GetMarkerRef().get();
marker_->StartMarking();
}
~WriteBarrierTest() override {
marker_->ClearAllWorklistsForTesting();
GetMarkerRef().reset();
}
MarkerBase* marker() const { return marker_; }
private:
Heap* internal_heap_;
MarkerBase* marker_;
};
class NoWriteBarrierTest : public testing::TestWithHeap {};
// =============================================================================
// Basic support. ==============================================================
// =============================================================================
TEST_F(WriteBarrierTest, EnableDisableIncrementalMarking) {
{
IncrementalMarkingScope scope(marker());
EXPECT_TRUE(WriteBarrier::IsEnabled());
}
}
TEST_F(WriteBarrierTest, TriggersWhenMarkingIsOn) {
auto* object1 = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto* object2 = MakeGarbageCollected<GCed>(GetAllocationHandle());
{
ExpectWriteBarrierFires scope(marker(), {object1});
EXPECT_FALSE(object1->IsMarked());
object2->set_next(object1);
EXPECT_TRUE(object1->IsMarked());
}
}
TEST_F(NoWriteBarrierTest, BailoutWhenMarkingIsOff) {
auto* object1 = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto* object2 = MakeGarbageCollected<GCed>(GetAllocationHandle());
EXPECT_FALSE(object1->IsMarked());
object2->set_next(object1);
EXPECT_FALSE(object1->IsMarked());
}
TEST_F(WriteBarrierTest, BailoutIfMarked) {
auto* object1 = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto* object2 = MakeGarbageCollected<GCed>(GetAllocationHandle());
EXPECT_TRUE(HeapObjectHeader::FromObject(object1).TryMarkAtomic());
{
ExpectNoWriteBarrierFires scope(marker(), {object1});
object2->set_next(object1);
}
}
TEST_F(WriteBarrierTest, MemberInitializingStoreNoBarrier) {
auto* object1 = MakeGarbageCollected<GCed>(GetAllocationHandle());
{
ExpectNoWriteBarrierFires scope(marker(), {object1});
auto* object2 = MakeGarbageCollected<GCed>(GetAllocationHandle(), object1);
HeapObjectHeader& object2_header = HeapObjectHeader::FromObject(object2);
EXPECT_FALSE(object2_header.IsMarked());
}
}
TEST_F(WriteBarrierTest, MemberReferenceAssignMember) {
auto* obj = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto* ref_obj = MakeGarbageCollected<GCed>(GetAllocationHandle());
Member<GCed>& m2 = ref_obj->next_ref();
Member<GCed> m3(obj);
{
ExpectWriteBarrierFires scope(marker(), {obj});
m2 = m3;
}
}
TEST_F(WriteBarrierTest, MemberSetSentinelValueNoBarrier) {
auto* obj = MakeGarbageCollected<GCed>(GetAllocationHandle());
Member<GCed>& m = obj->next_ref();
{
ExpectNoWriteBarrierFires scope(marker(), {});
m = kSentinelPointer;
}
}
TEST_F(WriteBarrierTest, MemberCopySentinelValueNoBarrier) {
auto* obj1 = MakeGarbageCollected<GCed>(GetAllocationHandle());
Member<GCed>& m1 = obj1->next_ref();
m1 = kSentinelPointer;
{
ExpectNoWriteBarrierFires scope(marker(), {});
auto* obj2 = MakeGarbageCollected<GCed>(GetAllocationHandle());
obj2->next_ref() = m1;
}
}
// =============================================================================
// Mixin support. ==============================================================
// =============================================================================
namespace {
class Mixin : public GarbageCollectedMixin {
public:
void Trace(cppgc::Visitor* visitor) const override { visitor->Trace(next_); }
virtual void Bar() {}
protected:
Member<GCed> next_;
};
class ClassWithVirtual {
protected:
virtual void Foo() {}
};
class Child : public GarbageCollected<Child>,
public ClassWithVirtual,
public Mixin {
public:
Child() : ClassWithVirtual(), Mixin() {}
~Child() = default;
void Trace(cppgc::Visitor* visitor) const override { Mixin::Trace(visitor); }
void Foo() override {}
void Bar() override {}
};
class ParentWithMixinPointer : public GarbageCollected<ParentWithMixinPointer> {
public:
ParentWithMixinPointer() = default;
void set_mixin(Mixin* mixin) { mixin_ = mixin; }
virtual void Trace(cppgc::Visitor* visitor) const { visitor->Trace(mixin_); }
protected:
Member<Mixin> mixin_;
};
} // namespace
TEST_F(WriteBarrierTest, WriteBarrierOnUnmarkedMixinApplication) {
ParentWithMixinPointer* parent =
MakeGarbageCollected<ParentWithMixinPointer>(GetAllocationHandle());
auto* child = MakeGarbageCollected<Child>(GetAllocationHandle());
Mixin* mixin = static_cast<Mixin*>(child);
EXPECT_NE(static_cast<void*>(child), static_cast<void*>(mixin));
{
ExpectWriteBarrierFires scope(marker(), {child});
parent->set_mixin(mixin);
}
}
TEST_F(WriteBarrierTest, NoWriteBarrierOnMarkedMixinApplication) {
ParentWithMixinPointer* parent =
MakeGarbageCollected<ParentWithMixinPointer>(GetAllocationHandle());
auto* child = MakeGarbageCollected<Child>(GetAllocationHandle());
EXPECT_TRUE(HeapObjectHeader::FromObject(child).TryMarkAtomic());
Mixin* mixin = static_cast<Mixin*>(child);
EXPECT_NE(static_cast<void*>(child), static_cast<void*>(mixin));
{
ExpectNoWriteBarrierFires scope(marker(), {child});
parent->set_mixin(mixin);
}
}
// =============================================================================
// Raw barriers. ===============================================================
// =============================================================================
using WriteBarrierParams = subtle::HeapConsistency::WriteBarrierParams;
using WriteBarrierType = subtle::HeapConsistency::WriteBarrierType;
using subtle::HeapConsistency;
TEST_F(NoWriteBarrierTest, WriteBarrierBailoutWhenMarkingIsOff) {
auto* object1 = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto* object2 = MakeGarbageCollected<GCed>(GetAllocationHandle(), object1);
{
EXPECT_FALSE(object1->IsMarked());
WriteBarrierParams params;
const WriteBarrierType expected =
Heap::From(GetHeap())->generational_gc_supported()
? WriteBarrierType::kGenerational
: WriteBarrierType::kNone;
EXPECT_EQ(expected, HeapConsistency::GetWriteBarrierType(
object2->next_ref().GetSlotForTesting(),
object2->next_ref().Get(), params));
EXPECT_FALSE(object1->IsMarked());
}
}
TEST_F(WriteBarrierTest, DijkstraWriteBarrierTriggersWhenMarkingIsOn) {
auto* object1 = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto* object2 = MakeGarbageCollected<GCed>(GetAllocationHandle(), object1);
{
ExpectWriteBarrierFires scope(marker(), {object1});
EXPECT_FALSE(object1->IsMarked());
WriteBarrierParams params;
EXPECT_EQ(WriteBarrierType::kMarking,
HeapConsistency::GetWriteBarrierType(
object2->next_ref().GetSlotForTesting(),
object2->next_ref().Get(), params));
HeapConsistency::DijkstraWriteBarrier(params, object2->next_ref().Get());
EXPECT_TRUE(object1->IsMarked());
}
}
TEST_F(WriteBarrierTest, DijkstraWriteBarrierBailoutIfMarked) {
auto* object1 = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto* object2 = MakeGarbageCollected<GCed>(GetAllocationHandle(), object1);
EXPECT_TRUE(HeapObjectHeader::FromObject(object1).TryMarkAtomic());
{
ExpectNoWriteBarrierFires scope(marker(), {object1});
WriteBarrierParams params;
EXPECT_EQ(WriteBarrierType::kMarking,
HeapConsistency::GetWriteBarrierType(
object2->next_ref().GetSlotForTesting(),
object2->next_ref().Get(), params));
HeapConsistency::DijkstraWriteBarrier(params, object2->next_ref().Get());
}
}
namespace {
struct InlinedObject {
CPPGC_DISALLOW_NEW();
void Trace(cppgc::Visitor* v) const { v->Trace(ref); }
Member<GCed> ref;
};
class GCedWithInlinedArray : public GarbageCollected<GCedWithInlinedArray> {
public:
static constexpr size_t kNumReferences = 4;
explicit GCedWithInlinedArray(GCed* value2) {
new (&objects[2].ref) Member<GCed>(value2);
}
void Trace(cppgc::Visitor* v) const {
for (size_t i = 0; i < kNumReferences; ++i) {
v->Trace(objects[i]);
}
}
InlinedObject objects[kNumReferences];
};
} // namespace
TEST_F(WriteBarrierTest, DijkstraWriteBarrierRangeTriggersWhenMarkingIsOn) {
auto* object1 = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto* object2 = MakeGarbageCollected<GCedWithInlinedArray>(
GetAllocationHandle(), object1);
{
ExpectWriteBarrierFires scope(marker(), {object1});
EXPECT_FALSE(object1->IsMarked());
WriteBarrierParams params;
EXPECT_EQ(WriteBarrierType::kMarking,
HeapConsistency::GetWriteBarrierType(
object2->objects, params, [this]() -> HeapHandle& {
return GetHeap()->GetHeapHandle();
}));
HeapConsistency::DijkstraWriteBarrierRange(
params, object2->objects, sizeof(InlinedObject), 4,
TraceTrait<InlinedObject>::Trace);
EXPECT_TRUE(object1->IsMarked());
}
}
TEST_F(WriteBarrierTest, DijkstraWriteBarrierRangeBailoutIfMarked) {
auto* object1 = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto* object2 = MakeGarbageCollected<GCedWithInlinedArray>(
GetAllocationHandle(), object1);
EXPECT_TRUE(HeapObjectHeader::FromObject(object1).TryMarkAtomic());
{
ExpectNoWriteBarrierFires scope(marker(), {object1});
WriteBarrierParams params;
EXPECT_EQ(WriteBarrierType::kMarking,
HeapConsistency::GetWriteBarrierType(
object2->objects, params, [this]() -> HeapHandle& {
return GetHeap()->GetHeapHandle();
}));
HeapConsistency::DijkstraWriteBarrierRange(
params, object2->objects, sizeof(InlinedObject), 4,
TraceTrait<InlinedObject>::Trace);
}
}
TEST_F(WriteBarrierTest, SteeleWriteBarrierTriggersWhenMarkingIsOn) {
auto* object1 = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto* object2 = MakeGarbageCollected<GCed>(GetAllocationHandle(), object1);
{
ExpectWriteBarrierFires scope(marker(), {object1});
EXPECT_TRUE(HeapObjectHeader::FromObject(object1).TryMarkAtomic());
WriteBarrierParams params;
EXPECT_EQ(WriteBarrierType::kMarking,
HeapConsistency::GetWriteBarrierType(
&object2->next_ref(), object2->next_ref().Get(), params));
HeapConsistency::SteeleWriteBarrier(params, object2->next_ref().Get());
}
}
TEST_F(WriteBarrierTest, SteeleWriteBarrierBailoutIfNotMarked) {
auto* object1 = MakeGarbageCollected<GCed>(GetAllocationHandle());
auto* object2 = MakeGarbageCollected<GCed>(GetAllocationHandle(), object1);
{
ExpectNoWriteBarrierFires scope(marker(), {object1});
WriteBarrierParams params;
EXPECT_EQ(WriteBarrierType::kMarking,
HeapConsistency::GetWriteBarrierType(
&object2->next_ref(), object2->next_ref().Get(), params));
HeapConsistency::SteeleWriteBarrier(params, object2->next_ref().Get());
}
}
} // namespace internal
} // namespace cppgc