diff --git a/BUILD.gn b/BUILD.gn index 4a82523504..4f413f8a65 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -4797,6 +4797,7 @@ v8_header_set("cppgc_headers") { "include/cppgc/custom-space.h", "include/cppgc/default-platform.h", "include/cppgc/ephemeron-pair.h", + "include/cppgc/explicit-management.h", "include/cppgc/garbage-collected.h", "include/cppgc/heap-consistency.h", "include/cppgc/heap-state.h", @@ -4849,6 +4850,7 @@ v8_source_set("cppgc_base") { "src/heap/cppgc/concurrent-marker.cc", "src/heap/cppgc/concurrent-marker.h", "src/heap/cppgc/default-platform.cc", + "src/heap/cppgc/explicit-management.cc", "src/heap/cppgc/free-list.cc", "src/heap/cppgc/free-list.h", "src/heap/cppgc/garbage-collector.h", diff --git a/include/cppgc/explicit-management.h b/include/cppgc/explicit-management.h new file mode 100644 index 0000000000..d2f13d6d9a --- /dev/null +++ b/include/cppgc/explicit-management.h @@ -0,0 +1,45 @@ +// 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. + +#ifndef INCLUDE_CPPGC_EXPLICIT_MANAGEMENT_H_ +#define INCLUDE_CPPGC_EXPLICIT_MANAGEMENT_H_ + +#include + +#include "cppgc/internal/logging.h" +#include "cppgc/type-traits.h" + +namespace cppgc { +namespace internal { + +V8_EXPORT void FreeUnreferencedObject(void*); + +} // namespace internal + +namespace subtle { + +/** + * Informs the garbage collector that `object` can be immediately reclaimed. The + * destructor may not be invoked immediately but only on next garbage + * collection. + * + * It is up to the embedder to guarantee that no other object holds a reference + * to `object` after calling `FreeUnreferencedObject()`. In case such a + * reference exists, it's use results in a use-after-free. + * + * \param object Reference to an object that is of type `GarbageCollected` and + * should be immediately reclaimed. + */ +template +void FreeUnreferencedObject(T* object) { + static_assert(IsGarbageCollectedTypeV, + "Object must be of type GarbageCollected."); + if (!object) return; + internal::FreeUnreferencedObject(object); +} + +} // namespace subtle +} // namespace cppgc + +#endif // INCLUDE_CPPGC_EXPLICIT_MANAGEMENT_H_ diff --git a/src/heap/cppgc/explicit-management.cc b/src/heap/cppgc/explicit-management.cc new file mode 100644 index 0000000000..dc1dbd6b2b --- /dev/null +++ b/src/heap/cppgc/explicit-management.cc @@ -0,0 +1,66 @@ +// 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/explicit-management.h" + +#include + +#include "src/heap/cppgc/heap-base.h" +#include "src/heap/cppgc/heap-object-header.h" +#include "src/heap/cppgc/heap-page.h" + +namespace cppgc { +namespace internal { + +namespace { + +std::pair CanExplicitlyFree(void* object) { + // object is guaranteed to be of type GarbageCollected, so getting the + // BasePage is okay for regular and large objects. + auto* base_page = BasePage::FromPayload(object); + auto* heap = base_page->heap(); + // Whenever the GC is active, avoid modifying the object as it may mess with + // state that the GC needs. + const bool in_gc = heap->in_atomic_pause() || heap->marker() || + heap->sweeper().IsSweepingInProgress(); + return {!in_gc, base_page}; +} + +} // namespace + +void FreeUnreferencedObject(void* object) { + bool can_free; + BasePage* base_page; + std::tie(can_free, base_page) = CanExplicitlyFree(object); + if (!can_free) { + return; + } + + auto& header = HeapObjectHeader::FromPayload(object); + header.Finalize(); + + if (base_page->is_large()) { // Large object. + base_page->space()->RemovePage(base_page); + base_page->heap()->stats_collector()->NotifyExplicitFree( + LargePage::From(base_page)->PayloadSize()); + LargePage::Destroy(LargePage::From(base_page)); + } else { // Regular object. + const size_t header_size = header.GetSize(); + auto* normal_page = NormalPage::From(base_page); + auto& normal_space = *static_cast(base_page->space()); + auto& lab = normal_space.linear_allocation_buffer(); + ConstAddress payload_end = header.PayloadEnd(); + SET_MEMORY_INACCESSIBLE(&header, header_size); + if (payload_end == lab.start()) { // Returning to LAB. + lab.Set(reinterpret_cast
(&header), lab.size() + header_size); + normal_page->object_start_bitmap().ClearBit(lab.start()); + } else { // Returning to free list. + base_page->heap()->stats_collector()->NotifyExplicitFree(header_size); + normal_space.free_list().Add({&header, header_size}); + } + } +} + +} // namespace internal +} // namespace cppgc diff --git a/src/heap/cppgc/heap-base.h b/src/heap/cppgc/heap-base.h index ed1668c993..6f4352d1c6 100644 --- a/src/heap/cppgc/heap-base.h +++ b/src/heap/cppgc/heap-base.h @@ -170,6 +170,8 @@ class V8_EXPORT_PRIVATE HeapBase : public cppgc::HeapHandle { stack_state_of_prev_gc_ = stack_state; } + void SetInAtomicPauseForTesting(bool value) { in_atomic_pause_ = value; } + protected: // Used by the incremental scheduler to finalize a GC if supported. virtual void FinalizeIncrementalGarbageCollectionIfNeeded( diff --git a/src/heap/cppgc/heap-object-header.h b/src/heap/cppgc/heap-object-header.h index 8d4362cab7..e369ab4573 100644 --- a/src/heap/cppgc/heap-object-header.h +++ b/src/heap/cppgc/heap-object-header.h @@ -64,6 +64,8 @@ class HeapObjectHeader { // The payload starts directly after the HeapObjectHeader. inline Address Payload() const; + template + inline Address PayloadEnd() const; template inline GCInfoIndex GetGCInfoIndex() const; @@ -182,6 +184,12 @@ Address HeapObjectHeader::Payload() const { sizeof(HeapObjectHeader); } +template +Address HeapObjectHeader::PayloadEnd() const { + return reinterpret_cast
(const_cast(this)) + + GetSize(); +} + template GCInfoIndex HeapObjectHeader::GetGCInfoIndex() const { const uint16_t encoded = diff --git a/src/heap/cppgc/stats-collector.cc b/src/heap/cppgc/stats-collector.cc index 49fd123951..fff5e23603 100644 --- a/src/heap/cppgc/stats-collector.cc +++ b/src/heap/cppgc/stats-collector.cc @@ -57,6 +57,10 @@ void StatsCollector::NotifySafePointForConservativeCollection() { } } +void StatsCollector::NotifySafePointForTesting() { + AllocatedObjectSizeSafepointImpl(); +} + void StatsCollector::AllocatedObjectSizeSafepointImpl() { allocated_bytes_since_end_of_marking_ += static_cast(allocated_bytes_since_safepoint_) - diff --git a/src/heap/cppgc/stats-collector.h b/src/heap/cppgc/stats-collector.h index ac819d31e4..2a8583c730 100644 --- a/src/heap/cppgc/stats-collector.h +++ b/src/heap/cppgc/stats-collector.h @@ -261,6 +261,8 @@ class V8_EXPORT_PRIVATE StatsCollector final { // their actual allocation/reclamation as possible. void NotifySafePointForConservativeCollection(); + void NotifySafePointForTesting(); + // Indicates a new garbage collection cycle. void NotifyMarkingStarted(CollectionType, IsForcedGC); // Indicates that marking of the current garbage collection cycle is diff --git a/test/unittests/BUILD.gn b/test/unittests/BUILD.gn index 53e68a03be..0a1a3085eb 100644 --- a/test/unittests/BUILD.gn +++ b/test/unittests/BUILD.gn @@ -92,6 +92,7 @@ v8_source_set("cppgc_unittests_sources") { "heap/cppgc/cross-thread-persistent-unittest.cc", "heap/cppgc/custom-spaces-unittest.cc", "heap/cppgc/ephemeron-pair-unittest.cc", + "heap/cppgc/explicit-management-unittest.cc", "heap/cppgc/finalizer-trait-unittest.cc", "heap/cppgc/free-list-unittest.cc", "heap/cppgc/garbage-collected-unittest.cc", diff --git a/test/unittests/heap/cppgc/explicit-management-unittest.cc b/test/unittests/heap/cppgc/explicit-management-unittest.cc new file mode 100644 index 0000000000..1e25f39f58 --- /dev/null +++ b/test/unittests/heap/cppgc/explicit-management-unittest.cc @@ -0,0 +1,119 @@ +// 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/explicit-management.h" + +#include "include/cppgc/garbage-collected.h" +#include "src/heap/cppgc/globals.h" +#include "src/heap/cppgc/heap-object-header.h" +#include "src/heap/cppgc/heap-space.h" +#include "src/heap/cppgc/page-memory.h" +#include "src/heap/cppgc/sweeper.h" +#include "test/unittests/heap/cppgc/tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace cppgc { +namespace internal { + +class ExplicitManagementTest : public testing::TestSupportingAllocationOnly { + public: + size_t AllocatedObjectSize() const { + auto* heap = Heap::From(GetHeap()); + heap->stats_collector()->NotifySafePointForTesting(); + return heap->stats_collector()->allocated_object_size(); + } + + void ResetLinearAllocationBuffers() const { + return Heap::From(GetHeap()) + ->object_allocator() + .ResetLinearAllocationBuffers(); + } +}; + +namespace { + +class DynamicallySized final : public GarbageCollected { + public: + void Trace(Visitor*) const {} +}; + +} // namespace + +TEST_F(ExplicitManagementTest, FreeRegularObjectToLAB) { + auto* o = + MakeGarbageCollected(GetHeap()->GetAllocationHandle()); + const auto* space = NormalPageSpace::From(BasePage::FromPayload(o)->space()); + const auto& lab = space->linear_allocation_buffer(); + auto& header = HeapObjectHeader::FromPayload(o); + const size_t size = header.GetSize(); + Address needle = reinterpret_cast
(&header); + // Test checks freeing to LAB. + ASSERT_EQ(lab.start(), header.PayloadEnd()); + const size_t lab_size_before_free = lab.size(); + const size_t allocated_size_before = AllocatedObjectSize(); + subtle::FreeUnreferencedObject(o); + EXPECT_EQ(lab.start(), reinterpret_cast
(needle)); + EXPECT_EQ(lab_size_before_free + size, lab.size()); + // LAB is included in allocated object size, so no change is expected. + EXPECT_EQ(allocated_size_before, AllocatedObjectSize()); + EXPECT_FALSE(space->free_list().ContainsForTesting({needle, size})); +} + +TEST_F(ExplicitManagementTest, FreeRegularObjectToFreeList) { + auto* o = + MakeGarbageCollected(GetHeap()->GetAllocationHandle()); + const auto* space = NormalPageSpace::From(BasePage::FromPayload(o)->space()); + const auto& lab = space->linear_allocation_buffer(); + auto& header = HeapObjectHeader::FromPayload(o); + const size_t size = header.GetSize(); + Address needle = reinterpret_cast
(&header); + // Test checks freeing to free list. + ResetLinearAllocationBuffers(); + ASSERT_EQ(lab.start(), nullptr); + const size_t allocated_size_before = AllocatedObjectSize(); + subtle::FreeUnreferencedObject(o); + EXPECT_EQ(lab.start(), nullptr); + EXPECT_EQ(allocated_size_before - size, AllocatedObjectSize()); + EXPECT_TRUE(space->free_list().ContainsForTesting({needle, size})); +} + +TEST_F(ExplicitManagementTest, FreeLargeObject) { + auto* o = MakeGarbageCollected( + GetHeap()->GetAllocationHandle(), + AdditionalBytes(kLargeObjectSizeThreshold)); + const auto* page = BasePage::FromPayload(o); + auto* heap = page->heap(); + ASSERT_TRUE(page->is_large()); + ConstAddress needle = reinterpret_cast(o); + const size_t size = LargePage::From(page)->PayloadSize(); + EXPECT_TRUE(heap->page_backend()->Lookup(needle)); + const size_t allocated_size_before = AllocatedObjectSize(); + subtle::FreeUnreferencedObject(o); + EXPECT_FALSE(heap->page_backend()->Lookup(needle)); + EXPECT_EQ(allocated_size_before - size, AllocatedObjectSize()); +} + +TEST_F(ExplicitManagementTest, FreeBailsOutDuringGC) { + const size_t snapshot_before = AllocatedObjectSize(); + auto* o = + MakeGarbageCollected(GetHeap()->GetAllocationHandle()); + auto* heap = BasePage::FromPayload(o)->heap(); + heap->SetInAtomicPauseForTesting(true); + const size_t allocated_size_before = AllocatedObjectSize(); + subtle::FreeUnreferencedObject(o); + EXPECT_EQ(allocated_size_before, AllocatedObjectSize()); + heap->SetInAtomicPauseForTesting(false); + ResetLinearAllocationBuffers(); + subtle::FreeUnreferencedObject(o); + EXPECT_EQ(snapshot_before, AllocatedObjectSize()); +} + +TEST_F(ExplicitManagementTest, FreeNull) { + DynamicallySized* o = nullptr; + // Noop. + subtle::FreeUnreferencedObject(o); +} + +} // namespace internal +} // namespace cppgc diff --git a/test/unittests/heap/cppgc/heap-object-header-unittest.cc b/test/unittests/heap/cppgc/heap-object-header-unittest.cc index 2621af2891..11f4498aa0 100644 --- a/test/unittests/heap/cppgc/heap-object-header-unittest.cc +++ b/test/unittests/heap/cppgc/heap-object-header-unittest.cc @@ -35,6 +35,14 @@ TEST(HeapObjectHeaderTest, Payload) { header.Payload()); } +TEST(HeapObjectHeaderTest, PayloadEnd) { + constexpr GCInfoIndex kGCInfoIndex = 17; + constexpr size_t kSize = kAllocationGranularity; + HeapObjectHeader header(kSize, kGCInfoIndex); + EXPECT_EQ(reinterpret_cast(&header) + kSize, + header.PayloadEnd()); +} + TEST(HeapObjectHeaderTest, GetGCInfoIndex) { constexpr GCInfoIndex kGCInfoIndex = 17; constexpr size_t kSize = kAllocationGranularity;