|  | // 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 |