// 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<GCed> { void Trace(cppgc::Visitor*) const {} }; class HeapAllocatedArray final : public GarbageCollected<HeapAllocatedArray> { 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<HeapAllocatedArray>(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<GCed>(GetAllocationHandle()); MakeGarbageCollected<GCed>(GetAllocationHandle()); GCed* p1 = MakeGarbageCollected<GCed>(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<GCed>(GetAllocationHandle()); if (p1 == p2) { reused_memory_found = true; break; } } EXPECT_TRUE(reused_memory_found); } namespace { class CallbackInCtor final : public GarbageCollected<CallbackInCtor> { public: template <typename Callback> explicit CallbackInCtor(Callback callback) { callback(); } void Trace(Visitor*) const {} }; } // namespace TEST_F(CppgcAllocationTest, ConservativeGCDuringAllocationDoesNotReclaimObject) { CallbackInCtor* obj = MakeGarbageCollected<CallbackInCtor>( GetAllocationHandle(), [this]() { ConservativeGC(); }); EXPECT_FALSE(HeapObjectHeader::FromObject(obj).IsFree()); } namespace { class LargeObject : public GarbageCollected<LargeObject> { 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<void*> pages; for (size_t i = 0; i < kNumObjects; ++i) { auto* obj = MakeGarbageCollected<LargeObject>(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<LargeObject>(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<DoubleWordAligned> { public: void Trace(Visitor*) const {} }; class alignas(kDoubleWord) LargeDoubleWordAligned : public GarbageCollected<LargeDoubleWordAligned> { public: virtual void Trace(cppgc::Visitor*) const {} char array[kLargeObjectSizeThreshold]; }; template <size_t Size> class CustomPadding final : public GarbageCollected<CustomPadding<Size>> { public: void Trace(cppgc::Visitor* visitor) const {} char base_size[128]; // Gets allocated in using RegularSpaceType::kNormal4. char padding[Size]; }; template <size_t Size> class alignas(kDoubleWord) AlignedCustomPadding final : public GarbageCollected<AlignedCustomPadding<Size>> { 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<DoubleWordAligned>(GetAllocationHandle()); EXPECT_EQ(0u, reinterpret_cast<uintptr_t>(gced) & kAlignmentMask); } TEST_F(CppgcAllocationTest, LargeDoubleWordAlignedAllocation) { static constexpr size_t kAlignmentMask = kDoubleWord - 1; auto* gced = MakeGarbageCollected<LargeDoubleWordAligned>(GetAllocationHandle()); EXPECT_EQ(0u, reinterpret_cast<uintptr_t>(gced) & kAlignmentMask); } TEST_F(CppgcAllocationTest, AlignToDoubleWordFromUnaligned) { static constexpr size_t kAlignmentMask = kDoubleWord - 1; auto* padding_object = MakeGarbageCollected<CustomPadding<kWord>>(GetAllocationHandle()); // The address from which the next object can be allocated, i.e. the end of // |padding_object|, should not be properly aligned. ASSERT_EQ(kWord, (reinterpret_cast<uintptr_t>(padding_object) + sizeof(*padding_object)) & kAlignmentMask); auto* aligned_object = MakeGarbageCollected<AlignedCustomPadding<16>>(GetAllocationHandle()); EXPECT_EQ(0u, reinterpret_cast<uintptr_t>(aligned_object) & kAlignmentMask); // Test only yielded a reliable result if objects are adjacent to each other. ASSERT_EQ(reinterpret_cast<uintptr_t>(padding_object) + sizeof(*padding_object) + sizeof(HeapObjectHeader), reinterpret_cast<uintptr_t>(aligned_object)); } TEST_F(CppgcAllocationTest, AlignToDoubleWordFromAligned) { static constexpr size_t kAlignmentMask = kDoubleWord - 1; auto* padding_object = MakeGarbageCollected<CustomPadding<16>>(GetAllocationHandle()); // The address from which the next object can be allocated, i.e. the end of // |padding_object|, should be properly aligned. ASSERT_EQ(0u, (reinterpret_cast<uintptr_t>(padding_object) + sizeof(*padding_object)) & kAlignmentMask); auto* aligned_object = MakeGarbageCollected<AlignedCustomPadding<16>>(GetAllocationHandle()); EXPECT_EQ(0u, reinterpret_cast<uintptr_t>(aligned_object) & kAlignmentMask); // Test only yielded a reliable result if objects are adjacent to each other. ASSERT_EQ(reinterpret_cast<uintptr_t>(padding_object) + sizeof(*padding_object) + 2 * sizeof(HeapObjectHeader), reinterpret_cast<uintptr_t>(aligned_object)); } } // namespace internal } // namespace cppgc