| // 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.GetSize()); |
| 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.Payload()); |
| } |
| |
| 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, GetSize) { |
| constexpr GCInfoIndex kGCInfoIndex = 17; |
| constexpr size_t kSize = kAllocationGranularity * 23; |
| HeapObjectHeader header(kSize, kGCInfoIndex); |
| EXPECT_EQ(kSize, header.GetSize()); |
| EXPECT_EQ(kSize, header.GetSize<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.GetSize()); |
| } |
| |
| 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; |
| typename std::aligned_storage<kSize, kAllocationGranularity>::type data; |
| HeapObjectHeader* header = new (&data) HeapObjectHeader(kSize, 1); |
| ConcurrentGCThread gc_thread(header, |
| reinterpret_cast<Payload*>(header->Payload())); |
| CHECK(gc_thread.Start()); |
| new (header->Payload()) 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 |