// 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 #include #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::TestWithHeap { public: using Age = AgeTable::Age; using AdjacentCardsPolicy = AgeTable::AdjacentCardsPolicy; static constexpr auto kCardSizeInBytes = AgeTable::kCardSizeInBytes; AgeTableTest() : disallow_gc_(GetHeapHandle()), age_table_(CagedHeapLocalData::Get().age_table) {} ~AgeTableTest() override { age_table_.ResetForTesting(); } NormalPage* AllocateNormalPage() { RawHeap& heap = Heap::From(GetHeap())->raw_heap(); auto* space = static_cast( heap.Space(RawHeap::RegularSpaceType::kNormal1)); auto* page = NormalPage::TryCreate(*Heap::From(GetHeap())->page_backend(), *space); CHECK_NOT_NULL(page); allocated_pages_.push_back({page, &BasePage::Destroy}); return page; } LargePage* AllocateLargePage() { constexpr size_t kObjectSize = 2 * kLargeObjectSizeThreshold; RawHeap& heap = Heap::From(GetHeap())->raw_heap(); auto* space = static_cast( 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, &BasePage::Destroy}); 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: subtle::DisallowGarbageCollectionScope disallow_gc_; std::vector> 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
( RoundUp(reinterpret_cast(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
(RoundUp( reinterpret_cast(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
(RoundUp( reinterpret_cast(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
( RoundUp(reinterpret_cast(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
( RoundUp(reinterpret_cast(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(CagedHeapBase::GetBase()); void* heap_end = heap_start + kCagedHeapReservationSize - 1; AssertAgeForAddressRange(heap_start, heap_end, Age::kOld); SetAgeForAddressRange(heap_start, heap_end, Age::kYoung, AdjacentCardsPolicy::kIgnore); AssertAgeForAddressRange(heap_start, heap_end, Age::kYoung); } } // namespace cppgc::internal