Commit 2a7bfabf authored by Omer Katz's avatar Omer Katz Committed by V8 LUCI CQ

cppgc: Allow allocations in prefinalziers

Prefinalizers have long been forbidden to allocate.
This restriction often proved problematic and has caused several
issues in the past.

This CL adds support for allowing allocations in prefinalizers.
At the start of prefinalizer invocations we clear the linear
allocation buffers, such that all allocations go through the slow
path for allocation. The slow path checks whether prefinalizers
are currently being invoked and marks the newly allocated object
if they are (i.e. black allocation during prefinalizers).

The new behavior is disabled by default and can be enabled by
setting the cppgc_allow_allocations_in_prefinalizers gn arg to true.

Bug: chromium:1056170
Change-Id: Ib86e780dcff88fa7b0f762ac2ab83c42393d33af
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3097877
Commit-Queue: Omer Katz <omerkatz@chromium.org>
Reviewed-by: 's avatarMichael Lippautz <mlippautz@chromium.org>
Cr-Commit-Position: refs/heads/main@{#76606}
parent e6961df2
...@@ -297,6 +297,9 @@ declare_args() { ...@@ -297,6 +297,9 @@ declare_args() {
# TODO(v8:11749): Enable by default after fixing any existing issues in Blink. # TODO(v8:11749): Enable by default after fixing any existing issues in Blink.
cppgc_enable_check_assignments_in_prefinalizers = false cppgc_enable_check_assignments_in_prefinalizers = false
# Enable allocations during prefinalizer invocations.
cppgc_allow_allocations_in_prefinalizers = false
# Enable young generation in cppgc. # Enable young generation in cppgc.
cppgc_enable_young_generation = false cppgc_enable_young_generation = false
...@@ -784,6 +787,10 @@ config("features") { ...@@ -784,6 +787,10 @@ config("features") {
defines += [ "CPPGC_CHECK_ASSIGNMENTS_IN_PREFINALIZERS" ] defines += [ "CPPGC_CHECK_ASSIGNMENTS_IN_PREFINALIZERS" ]
} }
if (cppgc_allow_allocations_in_prefinalizers) {
defines += [ "CPPGC_ALLOW_ALLOCATIONS_IN_PREFINALIZERS" ]
}
if (v8_embedder_string != "") { if (v8_embedder_string != "") {
defines += [ "V8_EMBEDDER_STRING=\"$v8_embedder_string\"" ] defines += [ "V8_EMBEDDER_STRING=\"$v8_embedder_string\"" ]
} }
......
...@@ -493,12 +493,14 @@ void CppHeap::TraceEpilogue(TraceSummary* trace_summary) { ...@@ -493,12 +493,14 @@ void CppHeap::TraceEpilogue(TraceSummary* trace_summary) {
// The allocated bytes counter in v8 was reset to the current marked bytes, so // The allocated bytes counter in v8 was reset to the current marked bytes, so
// any pending allocated bytes updates should be discarded. // any pending allocated bytes updates should be discarded.
buffered_allocated_bytes_ = 0; buffered_allocated_bytes_ = 0;
ExecutePreFinalizers(); const size_t bytes_allocated_in_prefinalizers = ExecutePreFinalizers();
#if CPPGC_VERIFY_HEAP #if CPPGC_VERIFY_HEAP
UnifiedHeapMarkingVerifier verifier(*this); UnifiedHeapMarkingVerifier verifier(*this);
verifier.Run(stack_state_of_prev_gc(), stack_end_of_current_gc(), verifier.Run(
stats_collector()->marked_bytes()); stack_state_of_prev_gc(), stack_end_of_current_gc(),
stats_collector()->marked_bytes() + bytes_allocated_in_prefinalizers);
#endif // CPPGC_VERIFY_HEAP #endif // CPPGC_VERIFY_HEAP
USE(bytes_allocated_in_prefinalizers);
{ {
cppgc::subtle::NoGarbageCollectionScope no_gc(*this); cppgc::subtle::NoGarbageCollectionScope no_gc(*this);
......
...@@ -75,8 +75,8 @@ HeapBase::HeapBase( ...@@ -75,8 +75,8 @@ HeapBase::HeapBase(
v8::base::Stack::GetStackStart())), v8::base::Stack::GetStackStart())),
prefinalizer_handler_(std::make_unique<PreFinalizerHandler>(*this)), prefinalizer_handler_(std::make_unique<PreFinalizerHandler>(*this)),
compactor_(raw_heap_), compactor_(raw_heap_),
object_allocator_(&raw_heap_, page_backend_.get(), object_allocator_(&raw_heap_, page_backend_.get(), stats_collector_.get(),
stats_collector_.get()), prefinalizer_handler_.get()),
sweeper_(*this), sweeper_(*this),
stack_support_(stack_support) { stack_support_(stack_support) {
stats_collector_->RegisterObserver( stats_collector_->RegisterObserver(
...@@ -100,10 +100,17 @@ size_t HeapBase::ObjectPayloadSize() const { ...@@ -100,10 +100,17 @@ size_t HeapBase::ObjectPayloadSize() const {
void HeapBase::AdvanceIncrementalGarbageCollectionOnAllocationIfNeeded() { void HeapBase::AdvanceIncrementalGarbageCollectionOnAllocationIfNeeded() {
if (marker_) marker_->AdvanceMarkingOnAllocation(); if (marker_) marker_->AdvanceMarkingOnAllocation();
} }
void HeapBase::ExecutePreFinalizers() {
size_t HeapBase::ExecutePreFinalizers() {
#ifdef CPPGC_ALLOW_ALLOCATIONS_IN_PREFINALIZERS
// Allocations in pre finalizers should not trigger another GC.
cppgc::subtle::NoGarbageCollectionScope no_gc_scope(*this);
#else
// Pre finalizers are forbidden from allocating objects. // Pre finalizers are forbidden from allocating objects.
cppgc::subtle::DisallowGarbageCollectionScope no_gc_scope(*this); cppgc::subtle::DisallowGarbageCollectionScope no_gc_scope(*this);
#endif // CPPGC_ALLOW_ALLOCATIONS_IN_PREFINALIZERS
prefinalizer_handler_->InvokePreFinalizers(); prefinalizer_handler_->InvokePreFinalizers();
return prefinalizer_handler_->ExtractBytesAllocatedInPrefinalizers();
} }
void HeapBase::Terminate() { void HeapBase::Terminate() {
......
...@@ -215,7 +215,8 @@ class V8_EXPORT_PRIVATE HeapBase : public cppgc::HeapHandle { ...@@ -215,7 +215,8 @@ class V8_EXPORT_PRIVATE HeapBase : public cppgc::HeapHandle {
bool IsMarking() const { return marker_.get(); } bool IsMarking() const { return marker_.get(); }
void ExecutePreFinalizers(); // Returns amount of bytes allocated while executing prefinalizers.
size_t ExecutePreFinalizers();
PageAllocator* page_allocator() const; PageAllocator* page_allocator() const;
......
...@@ -91,6 +91,8 @@ class HeapObjectHeader { ...@@ -91,6 +91,8 @@ class HeapObjectHeader {
void Unmark(); void Unmark();
inline bool TryMarkAtomic(); inline bool TryMarkAtomic();
inline void MarkNonAtomic();
template <AccessMode = AccessMode::kNonAtomic> template <AccessMode = AccessMode::kNonAtomic>
bool IsYoung() const; bool IsYoung() const;
...@@ -266,6 +268,11 @@ bool HeapObjectHeader::TryMarkAtomic() { ...@@ -266,6 +268,11 @@ bool HeapObjectHeader::TryMarkAtomic() {
std::memory_order_relaxed); std::memory_order_relaxed);
} }
void HeapObjectHeader::MarkNonAtomic() {
DCHECK(!IsMarked<AccessMode::kNonAtomic>());
encoded_low_ |= MarkBitField::encode(true);
}
template <AccessMode mode> template <AccessMode mode>
bool HeapObjectHeader::IsYoung() const { bool HeapObjectHeader::IsYoung() const {
return !IsMarked<mode>(); return !IsMarked<mode>();
......
...@@ -187,12 +187,17 @@ void Heap::FinalizeGarbageCollection(Config::StackState stack_state) { ...@@ -187,12 +187,17 @@ void Heap::FinalizeGarbageCollection(Config::StackState stack_state) {
marker_->FinishMarking(config_.stack_state); marker_->FinishMarking(config_.stack_state);
} }
marker_.reset(); marker_.reset();
ExecutePreFinalizers(); const size_t bytes_allocated_in_prefinalizers = ExecutePreFinalizers();
#if CPPGC_VERIFY_HEAP #if CPPGC_VERIFY_HEAP
MarkingVerifier verifier(*this); MarkingVerifier verifier(*this);
verifier.Run(config_.stack_state, stack_end_of_current_gc(), verifier.Run(
stats_collector()->marked_bytes()); config_.stack_state, stack_end_of_current_gc(),
stats_collector()->marked_bytes() + bytes_allocated_in_prefinalizers);
#endif // CPPGC_VERIFY_HEAP #endif // CPPGC_VERIFY_HEAP
#ifndef CPPGC_ALLOW_ALLOCATIONS_IN_PREFINALIZERS
DCHECK_EQ(0u, bytes_allocated_in_prefinalizers);
#endif
USE(bytes_allocated_in_prefinalizers);
subtle::NoGarbageCollectionScope no_gc(*this); subtle::NoGarbageCollectionScope no_gc(*this);
const Sweeper::SweepingConfig sweeping_config{ const Sweeper::SweepingConfig sweeping_config{
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#include "src/heap/cppgc/memory.h" #include "src/heap/cppgc/memory.h"
#include "src/heap/cppgc/object-start-bitmap.h" #include "src/heap/cppgc/object-start-bitmap.h"
#include "src/heap/cppgc/page-memory.h" #include "src/heap/cppgc/page-memory.h"
#include "src/heap/cppgc/prefinalizer-handler.h"
#include "src/heap/cppgc/stats-collector.h" #include "src/heap/cppgc/stats-collector.h"
#include "src/heap/cppgc/sweeper.h" #include "src/heap/cppgc/sweeper.h"
...@@ -102,16 +103,28 @@ void* AllocateLargeObject(PageBackend* page_backend, LargePageSpace* space, ...@@ -102,16 +103,28 @@ void* AllocateLargeObject(PageBackend* page_backend, LargePageSpace* space,
constexpr size_t ObjectAllocator::kSmallestSpaceSize; constexpr size_t ObjectAllocator::kSmallestSpaceSize;
ObjectAllocator::ObjectAllocator(RawHeap* heap, PageBackend* page_backend, ObjectAllocator::ObjectAllocator(RawHeap* heap, PageBackend* page_backend,
StatsCollector* stats_collector) StatsCollector* stats_collector,
PreFinalizerHandler* prefinalizer_handler)
: raw_heap_(heap), : raw_heap_(heap),
page_backend_(page_backend), page_backend_(page_backend),
stats_collector_(stats_collector) {} stats_collector_(stats_collector),
prefinalizer_handler_(prefinalizer_handler) {}
void* ObjectAllocator::OutOfLineAllocate(NormalPageSpace& space, size_t size, void* ObjectAllocator::OutOfLineAllocate(NormalPageSpace& space, size_t size,
GCInfoIndex gcinfo) { GCInfoIndex gcinfo) {
void* memory = OutOfLineAllocateImpl(space, size, gcinfo); void* memory = OutOfLineAllocateImpl(space, size, gcinfo);
stats_collector_->NotifySafePointForConservativeCollection(); stats_collector_->NotifySafePointForConservativeCollection();
raw_heap_->heap()->AdvanceIncrementalGarbageCollectionOnAllocationIfNeeded(); raw_heap_->heap()->AdvanceIncrementalGarbageCollectionOnAllocationIfNeeded();
if (prefinalizer_handler_->IsInvokingPreFinalizers()) {
// Objects allocated during pre finalizers should be allocated as black
// since marking is already done. Atomics are not needed because there is
// no concurrent marking in the background.
HeapObjectHeader::FromObject(memory).MarkNonAtomic();
// Resetting the allocation buffer forces all further allocations in pre
// finalizers to go through this slow path.
ReplaceLinearAllocationBuffer(space, *stats_collector_, nullptr, 0);
prefinalizer_handler_->NotifyAllocationInPrefinalizer(size);
}
return memory; return memory;
} }
......
...@@ -20,6 +20,7 @@ namespace cppgc { ...@@ -20,6 +20,7 @@ namespace cppgc {
namespace internal { namespace internal {
class ObjectAllocator; class ObjectAllocator;
class PreFinalizerHandler;
} // namespace internal } // namespace internal
class V8_EXPORT AllocationHandle { class V8_EXPORT AllocationHandle {
...@@ -38,7 +39,8 @@ class V8_EXPORT_PRIVATE ObjectAllocator final : public cppgc::AllocationHandle { ...@@ -38,7 +39,8 @@ class V8_EXPORT_PRIVATE ObjectAllocator final : public cppgc::AllocationHandle {
static constexpr size_t kSmallestSpaceSize = 32; static constexpr size_t kSmallestSpaceSize = 32;
ObjectAllocator(RawHeap* heap, PageBackend* page_backend, ObjectAllocator(RawHeap* heap, PageBackend* page_backend,
StatsCollector* stats_collector); StatsCollector* stats_collector,
PreFinalizerHandler* prefinalizer_handler);
inline void* AllocateObject(size_t size, GCInfoIndex gcinfo); inline void* AllocateObject(size_t size, GCInfoIndex gcinfo);
inline void* AllocateObject(size_t size, GCInfoIndex gcinfo, inline void* AllocateObject(size_t size, GCInfoIndex gcinfo,
...@@ -66,6 +68,7 @@ class V8_EXPORT_PRIVATE ObjectAllocator final : public cppgc::AllocationHandle { ...@@ -66,6 +68,7 @@ class V8_EXPORT_PRIVATE ObjectAllocator final : public cppgc::AllocationHandle {
RawHeap* raw_heap_; RawHeap* raw_heap_;
PageBackend* page_backend_; PageBackend* page_backend_;
StatsCollector* stats_collector_; StatsCollector* stats_collector_;
PreFinalizerHandler* prefinalizer_handler_;
}; };
void* ObjectAllocator::AllocateObject(size_t size, GCInfoIndex gcinfo) { void* ObjectAllocator::AllocateObject(size_t size, GCInfoIndex gcinfo) {
......
...@@ -31,7 +31,8 @@ bool PreFinalizerRegistrationDispatcher::PreFinalizer::operator==( ...@@ -31,7 +31,8 @@ bool PreFinalizerRegistrationDispatcher::PreFinalizer::operator==(
} }
PreFinalizerHandler::PreFinalizerHandler(HeapBase& heap) PreFinalizerHandler::PreFinalizerHandler(HeapBase& heap)
: heap_(heap) : current_ordered_pre_finalizers_(&ordered_pre_finalizers_),
heap_(heap)
#ifdef DEBUG #ifdef DEBUG
, ,
creation_thread_id_(v8::base::OS::GetCurrentThreadId()) creation_thread_id_(v8::base::OS::GetCurrentThreadId())
...@@ -44,7 +45,10 @@ void PreFinalizerHandler::RegisterPrefinalizer(PreFinalizer pre_finalizer) { ...@@ -44,7 +45,10 @@ void PreFinalizerHandler::RegisterPrefinalizer(PreFinalizer pre_finalizer) {
DCHECK_EQ(ordered_pre_finalizers_.end(), DCHECK_EQ(ordered_pre_finalizers_.end(),
std::find(ordered_pre_finalizers_.begin(), std::find(ordered_pre_finalizers_.begin(),
ordered_pre_finalizers_.end(), pre_finalizer)); ordered_pre_finalizers_.end(), pre_finalizer));
ordered_pre_finalizers_.push_back(pre_finalizer); DCHECK_EQ(current_ordered_pre_finalizers_->end(),
std::find(current_ordered_pre_finalizers_->begin(),
current_ordered_pre_finalizers_->end(), pre_finalizer));
current_ordered_pre_finalizers_->push_back(pre_finalizer);
} }
void PreFinalizerHandler::InvokePreFinalizers() { void PreFinalizerHandler::InvokePreFinalizers() {
...@@ -54,6 +58,13 @@ void PreFinalizerHandler::InvokePreFinalizers() { ...@@ -54,6 +58,13 @@ void PreFinalizerHandler::InvokePreFinalizers() {
DCHECK(CurrentThreadIsCreationThread()); DCHECK(CurrentThreadIsCreationThread());
LivenessBroker liveness_broker = LivenessBrokerFactory::Create(); LivenessBroker liveness_broker = LivenessBrokerFactory::Create();
is_invoking_ = true; is_invoking_ = true;
DCHECK_EQ(0u, bytes_allocated_in_prefinalizers);
// Reset all LABs to force allocations to the slow path for black allocation.
heap_.object_allocator().ResetLinearAllocationBuffers();
// Prefinalizers can allocate other objects with prefinalizers, which will
// modify ordered_pre_finalizers_ and break iterators.
std::vector<PreFinalizer> new_ordered_pre_finalizers;
current_ordered_pre_finalizers_ = &new_ordered_pre_finalizers;
ordered_pre_finalizers_.erase( ordered_pre_finalizers_.erase(
ordered_pre_finalizers_.begin(), ordered_pre_finalizers_.begin(),
std::remove_if(ordered_pre_finalizers_.rbegin(), std::remove_if(ordered_pre_finalizers_.rbegin(),
...@@ -62,6 +73,12 @@ void PreFinalizerHandler::InvokePreFinalizers() { ...@@ -62,6 +73,12 @@ void PreFinalizerHandler::InvokePreFinalizers() {
return (pf.callback)(liveness_broker, pf.object); return (pf.callback)(liveness_broker, pf.object);
}) })
.base()); .base());
// Newly added objects with prefinalizers will always survive the current GC
// cycle, so it's safe to add them after clearing out the older prefinalizers.
ordered_pre_finalizers_.insert(ordered_pre_finalizers_.end(),
new_ordered_pre_finalizers.begin(),
new_ordered_pre_finalizers.end());
current_ordered_pre_finalizers_ = &ordered_pre_finalizers_;
is_invoking_ = false; is_invoking_ = false;
ordered_pre_finalizers_.shrink_to_fit(); ordered_pre_finalizers_.shrink_to_fit();
} }
...@@ -74,5 +91,11 @@ bool PreFinalizerHandler::CurrentThreadIsCreationThread() { ...@@ -74,5 +91,11 @@ bool PreFinalizerHandler::CurrentThreadIsCreationThread() {
#endif #endif
} }
void PreFinalizerHandler::NotifyAllocationInPrefinalizer(size_t size) {
DCHECK_GT(bytes_allocated_in_prefinalizers + size,
bytes_allocated_in_prefinalizers);
bytes_allocated_in_prefinalizers += size;
}
} // namespace internal } // namespace internal
} // namespace cppgc } // namespace cppgc
...@@ -27,6 +27,11 @@ class PreFinalizerHandler final { ...@@ -27,6 +27,11 @@ class PreFinalizerHandler final {
bool IsInvokingPreFinalizers() const { return is_invoking_; } bool IsInvokingPreFinalizers() const { return is_invoking_; }
void NotifyAllocationInPrefinalizer(size_t);
size_t ExtractBytesAllocatedInPrefinalizers() {
return std::exchange(bytes_allocated_in_prefinalizers, 0);
}
private: private:
// Checks that the current thread is the thread that created the heap. // Checks that the current thread is the thread that created the heap.
bool CurrentThreadIsCreationThread(); bool CurrentThreadIsCreationThread();
...@@ -36,12 +41,16 @@ class PreFinalizerHandler final { ...@@ -36,12 +41,16 @@ class PreFinalizerHandler final {
// objects) for an object, by processing the ordered_pre_finalizers_ // objects) for an object, by processing the ordered_pre_finalizers_
// back-to-front. // back-to-front.
std::vector<PreFinalizer> ordered_pre_finalizers_; std::vector<PreFinalizer> ordered_pre_finalizers_;
std::vector<PreFinalizer>* current_ordered_pre_finalizers_;
HeapBase& heap_; HeapBase& heap_;
bool is_invoking_ = false; bool is_invoking_ = false;
#ifdef DEBUG #ifdef DEBUG
int creation_thread_id_; int creation_thread_id_;
#endif #endif
// Counter of bytes allocated during prefinalizers.
size_t bytes_allocated_in_prefinalizers = 0u;
}; };
} // namespace internal } // namespace internal
......
...@@ -246,16 +246,23 @@ class AllocatingPrefinalizer : public GarbageCollected<AllocatingPrefinalizer> { ...@@ -246,16 +246,23 @@ class AllocatingPrefinalizer : public GarbageCollected<AllocatingPrefinalizer> {
} // namespace } // namespace
#ifdef CPPGC_ALLOW_ALLOCATIONS_IN_PREFINALIZERS
TEST_F(PrefinalizerTest, PrefinalizerDoesNotFailOnAllcoation) {
auto* object = MakeGarbageCollected<AllocatingPrefinalizer>(
GetAllocationHandle(), GetHeap());
PreciseGC();
USE(object);
}
#else
#ifdef DEBUG #ifdef DEBUG
TEST_F(PrefinalizerDeathTest, PrefinalizerFailsOnAllcoation) { TEST_F(PrefinalizerDeathTest, PrefinalizerFailsOnAllcoation) {
auto* object = MakeGarbageCollected<AllocatingPrefinalizer>( auto* object = MakeGarbageCollected<AllocatingPrefinalizer>(
GetAllocationHandle(), GetHeap()); GetAllocationHandle(), GetHeap());
USE(object); USE(object);
EXPECT_DEATH_IF_SUPPORTED(PreciseGC(), ""); EXPECT_DEATH_IF_SUPPORTED(PreciseGC(), "");
} }
#endif // DEBUG #endif // DEBUG
#endif // CPPGC_ALLOW_ALLOCATIONS_IN_PREFINALIZERS
namespace { namespace {
...@@ -321,5 +328,17 @@ TEST_F(PrefinalizerDeathTest, PrefinalizerCantRessurectObjectOnHeap) { ...@@ -321,5 +328,17 @@ TEST_F(PrefinalizerDeathTest, PrefinalizerCantRessurectObjectOnHeap) {
#endif // CPPGC_CHECK_ASSIGNMENTS_IN_PREFINALIZERS #endif // CPPGC_CHECK_ASSIGNMENTS_IN_PREFINALIZERS
#endif // V8_ENABLE_CHECKS #endif // V8_ENABLE_CHECKS
#ifdef CPPGC_ALLOW_ALLOCATIONS_IN_PREFINALIZERS
TEST_F(PrefinalizerTest, AllocatingPrefinalizersInMultipleGCCycles) {
auto* object = MakeGarbageCollected<AllocatingPrefinalizer>(
GetAllocationHandle(), GetHeap());
PreciseGC();
auto* other_object = MakeGarbageCollected<AllocatingPrefinalizer>(
GetAllocationHandle(), GetHeap());
PreciseGC();
USE(object);
USE(other_object);
}
#endif
} // namespace internal } // namespace internal
} // namespace cppgc } // namespace cppgc
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment