| // 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 "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 "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, false}; } |
| |
| } // namespace |
| |
| TEST(GCInfoTableTest, InitialEmpty) { |
| v8::base::PageAllocator page_allocator; |
| GCInfoTable table(&page_allocator); |
| EXPECT_EQ(GCInfoTable::kMinIndex, table.NumberOfGCInfosForTesting()); |
| } |
| |
| TEST(GCInfoTableTest, ResizeToMaxIndex) { |
| v8::base::PageAllocator page_allocator; |
| GCInfoTable table(&page_allocator); |
| GCInfo info = GetEmptyGCInfo(); |
| for (GCInfoIndex i = GCInfoTable::kMinIndex; i < GCInfoTable::kMaxIndex; |
| i++) { |
| GCInfoIndex index = table.RegisterNewGCInfo(info); |
| EXPECT_EQ(i, index); |
| } |
| } |
| |
| TEST(GCInfoTableDeathTest, MoreThanMaxIndexInfos) { |
| v8::base::PageAllocator page_allocator; |
| GCInfoTable table(&page_allocator); |
| GCInfo info = GetEmptyGCInfo(); |
| // Create GCInfoTable::kMaxIndex entries. |
| for (GCInfoIndex i = GCInfoTable::kMinIndex; i < GCInfoTable::kMaxIndex; |
| i++) { |
| table.RegisterNewGCInfo(info); |
| } |
| EXPECT_DEATH_IF_SUPPORTED(table.RegisterNewGCInfo(info), ""); |
| } |
| |
| TEST(GCInfoTableDeathTest, OldTableAreaIsReadOnly) { |
| v8::base::PageAllocator page_allocator; |
| GCInfoTable table(&page_allocator); |
| 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++) { |
| table.RegisterNewGCInfo(info); |
| } |
| EXPECT_EQ(limit, table.LimitForTesting()); |
| table.RegisterNewGCInfo(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(GCInfoTable* table, |
| GCInfoIndex num_registrations) |
| : v8::base::Thread(Options("Thread registering GCInfo objects.")), |
| table_(table), |
| num_registrations_(num_registrations) {} |
| |
| void Run() final { |
| GCInfo info = GetEmptyGCInfo(); |
| for (GCInfoIndex i = 0; i < num_registrations_; i++) { |
| table_->RegisterNewGCInfo(info); |
| } |
| } |
| |
| private: |
| GCInfoTable* table_; |
| GCInfoIndex num_registrations_; |
| }; |
| |
| } // namespace |
| |
| TEST(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; |
| |
| v8::base::PageAllocator page_allocator; |
| GCInfoTable table(&page_allocator); |
| GCInfo info = GetEmptyGCInfo(); |
| for (size_t i = 0; i < main_thread_initialized; i++) { |
| table.RegisterNewGCInfo(info); |
| } |
| |
| v8::base::Thread* threads[num_threads]; |
| for (size_t i = 0; i < num_threads; i++) { |
| threads[i] = |
| new ThreadRegisteringGCInfoObjects(&table, 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 internal |
| } // namespace cppgc |