// 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 { void Trace(cppgc::Visitor*) const {} }; class HeapAllocatedArray final : public GarbageCollected { 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(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(GetAllocationHandle()); MakeGarbageCollected(GetAllocationHandle()); GCed* p1 = MakeGarbageCollected(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(GetAllocationHandle()); if (p1 == p2) { reused_memory_found = true; break; } } EXPECT_TRUE(reused_memory_found); } namespace { class CallbackInCtor final : public GarbageCollected { public: template explicit CallbackInCtor(Callback callback) { callback(); } void Trace(Visitor*) const {} }; } // namespace TEST_F(CppgcAllocationTest, ConservativeGCDuringAllocationDoesNotReclaimObject) { CallbackInCtor* obj = MakeGarbageCollected( GetAllocationHandle(), [this]() { ConservativeGC(); }); EXPECT_FALSE(HeapObjectHeader::FromObject(obj).IsFree()); } namespace { class LargeObject : public GarbageCollected { public: static constexpr size_t kDataSize = kLargeObjectSizeThreshold + 1; static size_t destructor_calls; explicit LargeObject(bool check) { if (!check) return; for (size_t i = 0; i < LargeObject::kDataSize; ++i) { EXPECT_EQ(0, data[i]); } } ~LargeObject() { ++destructor_calls; } void Trace(Visitor*) const {} char data[kDataSize]; }; size_t LargeObject::destructor_calls = 0u; } // namespace TEST_F(CppgcAllocationTest, LargePagesAreZeroedOut) { static constexpr size_t kNumObjects = 1u; LargeObject::destructor_calls = 0u; std::vector pages; for (size_t i = 0; i < kNumObjects; ++i) { auto* obj = MakeGarbageCollected(GetAllocationHandle(), false); pages.push_back(obj); memset(obj->data, 0xff, LargeObject::kDataSize); } PreciseGC(); EXPECT_EQ(kNumObjects, LargeObject::destructor_calls); bool reused_page = false; for (size_t i = 0; i < kNumObjects; ++i) { auto* obj = MakeGarbageCollected(GetAllocationHandle(), true); if (std::find(pages.begin(), pages.end(), obj) != pages.end()) reused_page = true; } EXPECT_TRUE(reused_page); } namespace { constexpr size_t kDoubleWord = 2 * sizeof(void*); constexpr size_t kWord = sizeof(void*); class alignas(kDoubleWord) DoubleWordAligned final : public GarbageCollected { public: void Trace(Visitor*) const {} }; class alignas(kDoubleWord) LargeDoubleWordAligned : public GarbageCollected { public: virtual void Trace(cppgc::Visitor*) const {} char array[kLargeObjectSizeThreshold]; }; template class CustomPadding final : public GarbageCollected> { public: void Trace(cppgc::Visitor* visitor) const {} char base_size[128]; // Gets allocated in using RegularSpaceType::kNormal4. char padding[Size]; }; template class alignas(kDoubleWord) AlignedCustomPadding final : public GarbageCollected> { 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(GetAllocationHandle()); EXPECT_EQ(0u, reinterpret_cast(gced) & kAlignmentMask); } TEST_F(CppgcAllocationTest, LargeDoubleWordAlignedAllocation) { static constexpr size_t kAlignmentMask = kDoubleWord - 1; auto* gced = MakeGarbageCollected(GetAllocationHandle()); EXPECT_EQ(0u, reinterpret_cast(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; static_assert(((sizeof(HeapObjectHeader) + sizeof(PaddingObject)) % kDoubleWord) == kWord); void* padding_object = nullptr; if (NormalPage::PayloadSize() % kDoubleWord == 0) { padding_object = MakeGarbageCollected(GetAllocationHandle()); ASSERT_EQ(kWord, (reinterpret_cast(padding_object) + sizeof(PaddingObject)) & kAlignmentMask); } auto* aligned_object = MakeGarbageCollected>(GetAllocationHandle()); EXPECT_EQ(0u, reinterpret_cast(aligned_object) & kAlignmentMask); if (padding_object) { // Test only yielded a reliable result if objects are adjacent to each // other. ASSERT_EQ(reinterpret_cast(padding_object) + sizeof(PaddingObject) + sizeof(HeapObjectHeader), reinterpret_cast(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; static_assert(((sizeof(HeapObjectHeader) + sizeof(PaddingObject)) % kDoubleWord) == kWord); void* padding_object = nullptr; if (NormalPage::PayloadSize() % kDoubleWord == kWord) { padding_object = MakeGarbageCollected(GetAllocationHandle()); ASSERT_EQ(0u, (reinterpret_cast(padding_object) + sizeof(PaddingObject)) & kAlignmentMask); } auto* aligned_object = MakeGarbageCollected>(GetAllocationHandle()); EXPECT_EQ(0u, reinterpret_cast(aligned_object) & kAlignmentMask); if (padding_object) { // Test only yielded a reliable result if objects are adjacent to each // other. ASSERT_EQ(reinterpret_cast(padding_object) + sizeof(PaddingObject) + 2 * sizeof(HeapObjectHeader), reinterpret_cast(aligned_object)); } } } // namespace internal } // namespace cppgc