%PDF- %PDF-
Direktori : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/deps/v8/src/heap/cppgc-js/ |
Current File : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/deps/v8/src/heap/cppgc-js/cpp-heap.cc |
// 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-js/cpp-heap.h" #include <cstdint> #include <memory> #include <numeric> #include "include/cppgc/heap-consistency.h" #include "include/cppgc/platform.h" #include "include/v8-isolate.h" #include "include/v8-local-handle.h" #include "include/v8-platform.h" #include "src/base/logging.h" #include "src/base/macros.h" #include "src/base/optional.h" #include "src/base/platform/time.h" #include "src/execution/isolate-inl.h" #include "src/flags/flags.h" #include "src/handles/handles.h" #include "src/handles/traced-handles.h" #include "src/heap/base/stack.h" #include "src/heap/cppgc-js/cpp-marking-state.h" #include "src/heap/cppgc-js/cpp-snapshot.h" #include "src/heap/cppgc-js/unified-heap-marking-state-inl.h" #include "src/heap/cppgc-js/unified-heap-marking-state.h" #include "src/heap/cppgc-js/unified-heap-marking-verifier.h" #include "src/heap/cppgc-js/unified-heap-marking-visitor.h" #include "src/heap/cppgc/concurrent-marker.h" #include "src/heap/cppgc/gc-info-table.h" #include "src/heap/cppgc/heap-base.h" #include "src/heap/cppgc/heap-object-header.h" #include "src/heap/cppgc/marker.h" #include "src/heap/cppgc/marking-state.h" #include "src/heap/cppgc/marking-visitor.h" #include "src/heap/cppgc/metric-recorder.h" #include "src/heap/cppgc/object-allocator.h" #include "src/heap/cppgc/platform.h" #include "src/heap/cppgc/prefinalizer-handler.h" #include "src/heap/cppgc/raw-heap.h" #include "src/heap/cppgc/stats-collector.h" #include "src/heap/cppgc/sweeper.h" #include "src/heap/cppgc/unmarker.h" #include "src/heap/cppgc/visitor.h" #include "src/heap/gc-tracer.h" #include "src/heap/heap.h" #include "src/heap/marking-worklist.h" #include "src/heap/minor-mark-sweep.h" #include "src/heap/traced-handles-marking-visitor.h" #include "src/init/v8.h" #include "src/profiler/heap-profiler.h" namespace v8 { namespace internal { class MinorGCHeapGrowing : public cppgc::internal::StatsCollector::AllocationObserver { public: explicit MinorGCHeapGrowing(cppgc::internal::StatsCollector& stats_collector) : stats_collector_(stats_collector) { stats_collector.RegisterObserver(this); } virtual ~MinorGCHeapGrowing() = default; void AllocatedObjectSizeIncreased(size_t) final {} void AllocatedObjectSizeDecreased(size_t) final {} void ResetAllocatedObjectSize(size_t allocated_object_size) final { ConfigureLimit(allocated_object_size); } bool LimitReached() const { return stats_collector_.allocated_object_size() >= limit_for_atomic_gc_; } private: void ConfigureLimit(size_t allocated_object_size) { // Constant growing factor for growing the heap limit. static constexpr double kGrowingFactor = 1.5; // For smaller heaps, allow allocating at least LAB in each regular space // before triggering GC again. static constexpr size_t kMinLimitIncrease = cppgc::internal::kPageSize * cppgc::internal::RawHeap::kNumberOfRegularSpaces; const size_t size = std::max(allocated_object_size, initial_heap_size_); limit_for_atomic_gc_ = std::max(static_cast<size_t>(size * kGrowingFactor), size + kMinLimitIncrease); } cppgc::internal::StatsCollector& stats_collector_; size_t initial_heap_size_ = 1 * cppgc::internal::kMB; size_t limit_for_atomic_gc_ = 0; // See ConfigureLimit(). }; } // namespace internal // static constexpr uint16_t WrapperDescriptor::kUnknownEmbedderId; // static std::unique_ptr<CppHeap> CppHeap::Create(v8::Platform* platform, const CppHeapCreateParams& params) { return std::make_unique<internal::CppHeap>( platform, params.custom_spaces, params.wrapper_descriptor, params.marking_support, params.sweeping_support); } cppgc::AllocationHandle& CppHeap::GetAllocationHandle() { return internal::CppHeap::From(this)->object_allocator(); } cppgc::HeapHandle& CppHeap::GetHeapHandle() { return *internal::CppHeap::From(this); } void CppHeap::Terminate() { internal::CppHeap::From(this)->Terminate(); } cppgc::HeapStatistics CppHeap::CollectStatistics( cppgc::HeapStatistics::DetailLevel detail_level) { return internal::CppHeap::From(this)->AsBase().CollectStatistics( detail_level); } void CppHeap::CollectCustomSpaceStatisticsAtLastGC( std::vector<cppgc::CustomSpaceIndex> custom_spaces, std::unique_ptr<CustomSpaceStatisticsReceiver> receiver) { return internal::CppHeap::From(this)->CollectCustomSpaceStatisticsAtLastGC( std::move(custom_spaces), std::move(receiver)); } void CppHeap::EnableDetachedGarbageCollectionsForTesting() { return internal::CppHeap::From(this) ->EnableDetachedGarbageCollectionsForTesting(); } void CppHeap::CollectGarbageForTesting(cppgc::EmbedderStackState stack_state) { return internal::CppHeap::From(this)->CollectGarbageForTesting( internal::CppHeap::CollectionType::kMajor, stack_state); } void CppHeap::CollectGarbageInYoungGenerationForTesting( cppgc::EmbedderStackState stack_state) { return internal::CppHeap::From(this)->CollectGarbageForTesting( internal::CppHeap::CollectionType::kMinor, stack_state); } v8::WrapperDescriptor CppHeap::wrapper_descriptor() const { return internal::CppHeap::From(this)->wrapper_descriptor(); } namespace internal { namespace { class CppgcPlatformAdapter final : public cppgc::Platform { public: explicit CppgcPlatformAdapter(v8::Platform* platform) : platform_(platform), page_allocator_(platform->GetPageAllocator() ? platform->GetPageAllocator() : &cppgc::internal::GetGlobalPageAllocator()) {} CppgcPlatformAdapter(const CppgcPlatformAdapter&) = delete; CppgcPlatformAdapter& operator=(const CppgcPlatformAdapter&) = delete; PageAllocator* GetPageAllocator() final { return page_allocator_; } double MonotonicallyIncreasingTime() final { return platform_->MonotonicallyIncreasingTime(); } std::shared_ptr<TaskRunner> GetForegroundTaskRunner() final { // If no Isolate has been set, there's no task runner to leverage for // foreground tasks. In detached mode the original platform handles the // task runner retrieval. if (!isolate_ && !is_in_detached_mode_) return nullptr; return platform_->GetForegroundTaskRunner(isolate_); } std::unique_ptr<JobHandle> PostJob(TaskPriority priority, std::unique_ptr<JobTask> job_task) final { return platform_->PostJob(priority, std::move(job_task)); } TracingController* GetTracingController() override { return platform_->GetTracingController(); } void SetIsolate(v8::Isolate* isolate) { isolate_ = isolate; } void EnableDetachedModeForTesting() { is_in_detached_mode_ = true; } private: v8::Platform* platform_; cppgc::PageAllocator* page_allocator_; v8::Isolate* isolate_ = nullptr; bool is_in_detached_mode_ = false; }; class UnifiedHeapConcurrentMarker : public cppgc::internal::ConcurrentMarkerBase { public: UnifiedHeapConcurrentMarker( cppgc::internal::HeapBase& heap, Heap* v8_heap, cppgc::internal::MarkingWorklists& marking_worklists, ::heap::base::IncrementalMarkingSchedule& incremental_marking_schedule, cppgc::Platform* platform, UnifiedHeapMarkingState& unified_heap_marking_state, CppHeap::CollectionType collection_type) : cppgc::internal::ConcurrentMarkerBase( heap, marking_worklists, incremental_marking_schedule, platform), v8_heap_(v8_heap), collection_type_(collection_type) {} std::unique_ptr<cppgc::Visitor> CreateConcurrentMarkingVisitor( cppgc::internal::ConcurrentMarkingState&) const final; private: Heap* const v8_heap_; CppHeap::CollectionType collection_type_; }; std::unique_ptr<cppgc::Visitor> UnifiedHeapConcurrentMarker::CreateConcurrentMarkingVisitor( cppgc::internal::ConcurrentMarkingState& marking_state) const { return std::make_unique<ConcurrentUnifiedHeapMarkingVisitor>( heap(), v8_heap_, marking_state, collection_type_); } void FatalOutOfMemoryHandlerImpl(const std::string& reason, const SourceLocation&, HeapBase* heap) { V8::FatalProcessOutOfMemory( static_cast<v8::internal::CppHeap*>(heap)->isolate(), reason.c_str()); } void GlobalFatalOutOfMemoryHandlerImpl(const std::string& reason, const SourceLocation&, HeapBase* heap) { V8::FatalProcessOutOfMemory(nullptr, reason.c_str()); } class UnifiedHeapConservativeMarkingVisitor final : public cppgc::internal::ConservativeMarkingVisitor { public: UnifiedHeapConservativeMarkingVisitor( HeapBase& heap, MutatorMarkingState& mutator_marking_state, cppgc::Visitor& visitor) : ConservativeMarkingVisitor(heap, mutator_marking_state, visitor) {} ~UnifiedHeapConservativeMarkingVisitor() override = default; void SetConservativeTracedHandlesMarkingVisitor( std::unique_ptr<ConservativeTracedHandlesMarkingVisitor> global_handle_marking_visitor) { marking_visitor_ = std::move(global_handle_marking_visitor); } void TraceConservativelyIfNeeded(const void* address) override { ConservativeMarkingVisitor::TraceConservativelyIfNeeded(address); if (marking_visitor_) { marking_visitor_->VisitPointer(address); } } private: std::unique_ptr<ConservativeTracedHandlesMarkingVisitor> marking_visitor_; }; } // namespace class UnifiedHeapMarker final : public cppgc::internal::MarkerBase { public: UnifiedHeapMarker(Heap* v8_heap, cppgc::internal::HeapBase& cpp_heap, cppgc::Platform* platform, cppgc::internal::MarkingConfig config); ~UnifiedHeapMarker() final = default; void AddObject(void*); cppgc::internal::MarkingWorklists& GetMarkingWorklists() { return marking_worklists_; } cppgc::internal::MutatorMarkingState& GetMutatorMarkingState() { return static_cast<cppgc::internal::MutatorMarkingState&>( marking_visitor_->marking_state_); } UnifiedHeapMarkingState& GetMutatorUnifiedHeapMarkingState() { return mutator_unified_heap_marking_state_; } UnifiedHeapConservativeMarkingVisitor& conservative_visitor() final { return conservative_marking_visitor_; } protected: cppgc::Visitor& visitor() final { return *marking_visitor_; } ::heap::base::StackVisitor& stack_visitor() final { return conservative_marking_visitor_; } private: UnifiedHeapMarkingState mutator_unified_heap_marking_state_; std::unique_ptr<MutatorUnifiedHeapMarkingVisitor> marking_visitor_; UnifiedHeapConservativeMarkingVisitor conservative_marking_visitor_; }; UnifiedHeapMarker::UnifiedHeapMarker(Heap* v8_heap, cppgc::internal::HeapBase& heap, cppgc::Platform* platform, cppgc::internal::MarkingConfig config) : cppgc::internal::MarkerBase(heap, platform, config), mutator_unified_heap_marking_state_(v8_heap, nullptr, config.collection_type), marking_visitor_(std::make_unique<MutatorUnifiedHeapMarkingVisitor>( heap, mutator_marking_state_, mutator_unified_heap_marking_state_)), conservative_marking_visitor_(heap, mutator_marking_state_, *marking_visitor_) { concurrent_marker_ = std::make_unique<UnifiedHeapConcurrentMarker>( heap_, v8_heap, marking_worklists_, *schedule_, platform_, mutator_unified_heap_marking_state_, config.collection_type); } void UnifiedHeapMarker::AddObject(void* object) { mutator_marking_state_.MarkAndPush( cppgc::internal::HeapObjectHeader::FromObject(object)); } void CppHeap::MetricRecorderAdapter::AddMainThreadEvent( const GCCycle& cppgc_event) { auto* tracer = GetIsolate()->heap()->tracer(); if (cppgc_event.type == MetricRecorder::GCCycle::Type::kMinor) { DCHECK(!last_young_gc_event_); last_young_gc_event_ = cppgc_event; tracer->NotifyYoungCppGCCompleted(); } else { DCHECK(!last_full_gc_event_); last_full_gc_event_ = cppgc_event; tracer->NotifyFullCppGCCompleted(); } } void CppHeap::MetricRecorderAdapter::AddMainThreadEvent( const MainThreadIncrementalMark& cppgc_event) { // Incremental marking steps might be nested in V8 marking steps. In such // cases, stash the relevant values and delegate to V8 to report them. For // non-nested steps, report to the Recorder directly. if (cpp_heap_.is_in_v8_marking_step_) { last_incremental_mark_event_ = cppgc_event; return; } // This is a standalone incremental marking step. const std::shared_ptr<metrics::Recorder>& recorder = GetIsolate()->metrics_recorder(); DCHECK_NOT_NULL(recorder); if (!recorder->HasEmbedderRecorder()) return; incremental_mark_batched_events_.events.emplace_back(); incremental_mark_batched_events_.events.back().cpp_wall_clock_duration_in_us = cppgc_event.duration_us; if (incremental_mark_batched_events_.events.size() == kMaxBatchedEvents) { recorder->AddMainThreadEvent(std::move(incremental_mark_batched_events_), GetContextId()); incremental_mark_batched_events_ = {}; } } void CppHeap::MetricRecorderAdapter::AddMainThreadEvent( const MainThreadIncrementalSweep& cppgc_event) { // Incremental sweeping steps are never nested inside V8 sweeping steps, so // report to the Recorder directly. const std::shared_ptr<metrics::Recorder>& recorder = GetIsolate()->metrics_recorder(); DCHECK_NOT_NULL(recorder); if (!recorder->HasEmbedderRecorder()) return; incremental_sweep_batched_events_.events.emplace_back(); incremental_sweep_batched_events_.events.back() .cpp_wall_clock_duration_in_us = cppgc_event.duration_us; if (incremental_sweep_batched_events_.events.size() == kMaxBatchedEvents) { recorder->AddMainThreadEvent(std::move(incremental_sweep_batched_events_), GetContextId()); incremental_sweep_batched_events_ = {}; } } void CppHeap::MetricRecorderAdapter::FlushBatchedIncrementalEvents() { const std::shared_ptr<metrics::Recorder>& recorder = GetIsolate()->metrics_recorder(); DCHECK_NOT_NULL(recorder); if (!incremental_mark_batched_events_.events.empty()) { recorder->AddMainThreadEvent(std::move(incremental_mark_batched_events_), GetContextId()); incremental_mark_batched_events_ = {}; } if (!incremental_sweep_batched_events_.events.empty()) { recorder->AddMainThreadEvent(std::move(incremental_sweep_batched_events_), GetContextId()); incremental_sweep_batched_events_ = {}; } } bool CppHeap::MetricRecorderAdapter::FullGCMetricsReportPending() const { return last_full_gc_event_.has_value(); } bool CppHeap::MetricRecorderAdapter::YoungGCMetricsReportPending() const { return last_young_gc_event_.has_value(); } const base::Optional<cppgc::internal::MetricRecorder::GCCycle> CppHeap::MetricRecorderAdapter::ExtractLastFullGcEvent() { auto res = std::move(last_full_gc_event_); last_full_gc_event_.reset(); return res; } const base::Optional<cppgc::internal::MetricRecorder::GCCycle> CppHeap::MetricRecorderAdapter::ExtractLastYoungGcEvent() { auto res = std::move(last_young_gc_event_); last_young_gc_event_.reset(); return res; } const base::Optional<cppgc::internal::MetricRecorder::MainThreadIncrementalMark> CppHeap::MetricRecorderAdapter::ExtractLastIncrementalMarkEvent() { auto res = std::move(last_incremental_mark_event_); last_incremental_mark_event_.reset(); return res; } void CppHeap::MetricRecorderAdapter::ClearCachedEvents() { incremental_mark_batched_events_.events.clear(); incremental_sweep_batched_events_.events.clear(); last_incremental_mark_event_.reset(); last_full_gc_event_.reset(); last_young_gc_event_.reset(); } Isolate* CppHeap::MetricRecorderAdapter::GetIsolate() const { DCHECK_NOT_NULL(cpp_heap_.isolate()); return reinterpret_cast<Isolate*>(cpp_heap_.isolate()); } v8::metrics::Recorder::ContextId CppHeap::MetricRecorderAdapter::GetContextId() const { DCHECK_NOT_NULL(GetIsolate()); if (GetIsolate()->context().is_null()) return v8::metrics::Recorder::ContextId::Empty(); HandleScope scope(GetIsolate()); return GetIsolate()->GetOrRegisterRecorderContextId( GetIsolate()->native_context()); } // static void CppHeap::InitializeOncePerProcess() { cppgc::internal::GetGlobalOOMHandler().SetCustomHandler( &GlobalFatalOutOfMemoryHandlerImpl); } CppHeap::CppHeap( v8::Platform* platform, const std::vector<std::unique_ptr<cppgc::CustomSpaceBase>>& custom_spaces, const v8::WrapperDescriptor& wrapper_descriptor, cppgc::Heap::MarkingType marking_support, cppgc::Heap::SweepingType sweeping_support) : cppgc::internal::HeapBase( std::make_shared<CppgcPlatformAdapter>(platform), custom_spaces, cppgc::internal::HeapBase::StackSupport:: kSupportsConservativeStackScan, marking_support, sweeping_support, *this), minor_gc_heap_growing_( std::make_unique<MinorGCHeapGrowing>(*stats_collector())), cross_heap_remembered_set_(*this), wrapper_descriptor_(wrapper_descriptor) { CHECK_NE(WrapperDescriptor::kUnknownEmbedderId, wrapper_descriptor_.embedder_id_for_garbage_collected); // Enter no GC scope. `AttachIsolate()` removes this and allows triggering // garbage collections. no_gc_scope_++; stats_collector()->RegisterObserver(this); } CppHeap::~CppHeap() { if (isolate_) { isolate_->heap()->DetachCppHeap(); } } void CppHeap::Terminate() { // Must not be attached to a heap when invoking termination GCs. CHECK(!isolate_); // Gracefully terminate the C++ heap invoking destructors. HeapBase::Terminate(); } namespace { class SweepingOnMutatorThreadForGlobalHandlesScope final { public: explicit SweepingOnMutatorThreadForGlobalHandlesScope( TracedHandles& traced_handles) : traced_handles_(traced_handles) { traced_handles_.SetIsSweepingOnMutatorThread(true); } ~SweepingOnMutatorThreadForGlobalHandlesScope() { traced_handles_.SetIsSweepingOnMutatorThread(false); } TracedHandles& traced_handles_; }; class SweepingOnMutatorThreadForGlobalHandlesObserver final : public cppgc::internal::Sweeper::SweepingOnMutatorThreadObserver { public: SweepingOnMutatorThreadForGlobalHandlesObserver(CppHeap& cpp_heap, TracedHandles& traced_handles) : cppgc::internal::Sweeper::SweepingOnMutatorThreadObserver( cpp_heap.sweeper()), traced_handles_(traced_handles) {} void Start() override { traced_handles_.SetIsSweepingOnMutatorThread(true); } void End() override { traced_handles_.SetIsSweepingOnMutatorThread(false); } private: TracedHandles& traced_handles_; }; class MoveListenerImpl final : public HeapProfilerNativeMoveListener, public cppgc::internal::MoveListener { public: MoveListenerImpl(HeapProfiler* profiler, CppHeap* heap) : HeapProfilerNativeMoveListener(profiler), heap_(heap) {} ~MoveListenerImpl() { if (active_) { heap_->UnregisterMoveListener(this); } } // HeapProfilerNativeMoveListener implementation: void StartListening() override { if (active_) return; active_ = true; heap_->RegisterMoveListener(this); } void StopListening() override { if (!active_) return; active_ = false; heap_->UnregisterMoveListener(this); } // cppgc::internal::MoveListener implementation: void OnMove(uint8_t* from, uint8_t* to, size_t size_including_header) override { ObjectMoveEvent(reinterpret_cast<Address>(from), reinterpret_cast<Address>(to), static_cast<int>(size_including_header)); } private: CppHeap* heap_; bool active_ = false; }; } // namespace void CppHeap::AttachIsolate(Isolate* isolate) { CHECK(!in_detached_testing_mode_); CHECK_NULL(isolate_); isolate_ = isolate; heap_ = isolate->heap(); static_cast<CppgcPlatformAdapter*>(platform()) ->SetIsolate(reinterpret_cast<v8::Isolate*>(isolate_)); if (auto* heap_profiler = isolate_->heap_profiler()) { heap_profiler->AddBuildEmbedderGraphCallback(&CppGraphBuilder::Run, this); heap_profiler->set_native_move_listener( std::make_unique<MoveListenerImpl>(heap_profiler, this)); } SetMetricRecorder(std::make_unique<MetricRecorderAdapter>(*this)); oom_handler().SetCustomHandler(&FatalOutOfMemoryHandlerImpl); UpdateGCCapabilitiesFromFlags(); sweeping_on_mutator_thread_observer_ = std::make_unique<SweepingOnMutatorThreadForGlobalHandlesObserver>( *this, *isolate_->traced_handles()); no_gc_scope_--; } void CppHeap::DetachIsolate() { // TODO(chromium:1056170): Investigate whether this can be enforced with a // CHECK across all relevant embedders and setups. if (!isolate_) return; // Finish any ongoing garbage collection. if (isolate_->heap()->incremental_marking()->IsMarking()) { isolate_->heap()->FinalizeIncrementalMarkingAtomically( i::GarbageCollectionReason::kExternalFinalize); } sweeper_.FinishIfRunning(); sweeping_on_mutator_thread_observer_.reset(); if (auto* heap_profiler = isolate_->heap_profiler()) { heap_profiler->RemoveBuildEmbedderGraphCallback(&CppGraphBuilder::Run, this); heap_profiler->set_native_move_listener(nullptr); } SetMetricRecorder(nullptr); isolate_ = nullptr; heap_ = nullptr; // Any future garbage collections will ignore the V8->C++ references. oom_handler().SetCustomHandler(nullptr); // Enter no GC scope. no_gc_scope_++; } ::heap::base::Stack* CppHeap::stack() { return isolate_ ? &isolate_->heap()->stack() : HeapBase::stack(); } namespace { bool IsMemoryReducingGC(CppHeap::GarbageCollectionFlags flags) { return flags & CppHeap::GarbageCollectionFlagValues::kReduceMemory; } bool IsForceGC(CppHeap::GarbageCollectionFlags flags) { return flags & CppHeap::GarbageCollectionFlagValues::kForced; } bool ShouldReduceMemory(CppHeap::GarbageCollectionFlags flags) { return IsMemoryReducingGC(flags) || IsForceGC(flags); } constexpr size_t kIncrementalMarkingCheckInterval = 128 * KB; } // namespace CppHeap::MarkingType CppHeap::SelectMarkingType() const { // For now, force atomic marking for minor collections. if (*collection_type_ == CollectionType::kMinor) return MarkingType::kAtomic; if (IsForceGC(current_gc_flags_) && !force_incremental_marking_for_testing_) return MarkingType::kAtomic; const MarkingType marking_type = marking_support(); if (marking_type == MarkingType::kIncrementalAndConcurrent && heap_ && !heap_->ShouldUseBackgroundThreads()) { return MarkingType::kIncremental; } return marking_support(); } CppHeap::SweepingType CppHeap::SelectSweepingType() const { if (IsForceGC(current_gc_flags_)) return SweepingType::kAtomic; return sweeping_support(); } void CppHeap::UpdateGCCapabilitiesFromFlags() { CHECK_IMPLIES(v8_flags.cppheap_concurrent_marking, v8_flags.cppheap_incremental_marking); if (v8_flags.cppheap_concurrent_marking) { marking_support_ = static_cast<MarkingType>( std::min(marking_support_, MarkingType::kIncrementalAndConcurrent)); } else if (v8_flags.cppheap_incremental_marking) { marking_support_ = static_cast<MarkingType>( std::min(marking_support_, MarkingType::kIncremental)); } else { marking_support_ = MarkingType::kAtomic; } sweeping_support_ = v8_flags.single_threaded_gc ? CppHeap::SweepingType::kIncremental : CppHeap::SweepingType::kIncrementalAndConcurrent; } void CppHeap::InitializeTracing(CollectionType collection_type, GarbageCollectionFlags gc_flags) { DCHECK(!collection_type_); if (collection_type == CollectionType::kMinor) { if (!generational_gc_supported()) return; // Notify GC tracer that CppGC started young GC cycle. isolate_->heap()->tracer()->NotifyYoungCppGCRunning(); } collection_type_ = collection_type; CHECK(!sweeper_.IsSweepingInProgress()); // Check that previous cycle metrics for the same collection type have been // reported. if (GetMetricRecorder()) { if (collection_type == CollectionType::kMajor) DCHECK(!GetMetricRecorder()->FullGCMetricsReportPending()); else DCHECK(!GetMetricRecorder()->YoungGCMetricsReportPending()); } #if defined(CPPGC_YOUNG_GENERATION) if (generational_gc_supported() && *collection_type_ == CollectionType::kMajor) { stats_collector()->NotifyUnmarkingStarted(*collection_type_); cppgc::internal::StatsCollector::EnabledScope stats_scope( stats_collector(), cppgc::internal::StatsCollector::kUnmark); cppgc::internal::SequentialUnmarker unmarker(raw_heap()); } #endif // defined(CPPGC_YOUNG_GENERATION) if (gc_flags == GarbageCollectionFlagValues::kNoFlags) { if (heap()->is_current_gc_forced()) { gc_flags |= CppHeap::GarbageCollectionFlagValues::kForced; } if (heap()->ShouldReduceMemory()) { gc_flags |= CppHeap::GarbageCollectionFlagValues::kReduceMemory; } } current_gc_flags_ = gc_flags; const cppgc::internal::MarkingConfig marking_config{ *collection_type_, StackState::kNoHeapPointers, SelectMarkingType(), IsForceGC(current_gc_flags_) ? cppgc::internal::MarkingConfig::IsForcedGC::kForced : cppgc::internal::MarkingConfig::IsForcedGC::kNotForced, v8::base::TimeDelta::FromMilliseconds( v8_flags.incremental_marking_task_delay_ms), v8_flags.incremental_marking_bailout_when_ahead_of_schedule}; DCHECK_IMPLIES(!isolate_, (MarkingType::kAtomic == marking_config.marking_type) || force_incremental_marking_for_testing_); if (ShouldReduceMemory(current_gc_flags_)) { // Only enable compaction when in a memory reduction garbage collection as // it may significantly increase the final garbage collection pause. compactor_.InitializeIfShouldCompact(marking_config.marking_type, marking_config.stack_state); } marker_ = std::make_unique<UnifiedHeapMarker>( isolate_ ? isolate()->heap() : nullptr, AsBase(), platform_.get(), marking_config); } namespace { MarkingWorklists::Local* GetV8MarkingWorklists( Isolate* isolate, cppgc::internal::CollectionType collection_type) { auto* heap = isolate->heap(); if (collection_type == cppgc::internal::CollectionType::kMajor) { return heap->mark_compact_collector()->local_marking_worklists(); } else { return heap->minor_mark_sweep_collector()->local_marking_worklists(); } } } // namespace void CppHeap::StartTracing() { CHECK(marking_done_); if (!TracingInitialized()) return; if (isolate_) { // Reuse the same local worklist for the mutator marking state which results // in directly processing the objects by the JS logic. Also avoids // publishing local objects. marker_.get() ->To<UnifiedHeapMarker>() .GetMutatorUnifiedHeapMarkingState() .Update(GetV8MarkingWorklists(isolate_, *collection_type_)); } marker_->StartMarking(); marking_done_ = false; } bool CppHeap::AdvanceTracing(v8::base::TimeDelta max_duration) { if (!TracingInitialized()) return true; is_in_v8_marking_step_ = true; cppgc::internal::StatsCollector::EnabledScope stats_scope( stats_collector(), in_atomic_pause_ ? cppgc::internal::StatsCollector::kAtomicMark : cppgc::internal::StatsCollector::kIncrementalMark); const v8::base::TimeDelta deadline = in_atomic_pause_ ? v8::base::TimeDelta::Max() : max_duration; const size_t marked_bytes_limit = in_atomic_pause_ ? SIZE_MAX : 0; DCHECK_NOT_NULL(marker_); if (in_atomic_pause_) { marker_->NotifyConcurrentMarkingOfWorkIfNeeded( cppgc::TaskPriority::kUserBlocking); } // TODO(chromium:1056170): Replace when unified heap transitions to // bytes-based deadline. marking_done_ = marker_->AdvanceMarkingWithLimits(deadline, marked_bytes_limit); DCHECK_IMPLIES(in_atomic_pause_, marking_done_); is_in_v8_marking_step_ = false; return marking_done_; } bool CppHeap::IsTracingDone() const { return !TracingInitialized() || marking_done_; } bool CppHeap::ShouldFinalizeIncrementalMarking() const { return !incremental_marking_supported() || IsTracingDone(); } void CppHeap::EnterFinalPause(cppgc::EmbedderStackState stack_state) { CHECK(!in_disallow_gc_scope()); // Enter atomic pause even if tracing is not initialized. This is needed to // make sure that we always enable young generation from the atomic pause. in_atomic_pause_ = true; if (!TracingInitialized()) return; auto& marker = marker_.get()->To<UnifiedHeapMarker>(); // Scan global handles conservatively in case we are attached to an Isolate. // TODO(1029379): Support global handle marking visitors with minor GC. if (isolate_) { auto& heap = *isolate()->heap(); marker.conservative_visitor().SetConservativeTracedHandlesMarkingVisitor( std::make_unique<ConservativeTracedHandlesMarkingVisitor>( heap, *GetV8MarkingWorklists(isolate_, *collection_type_), *collection_type_)); } marker.EnterAtomicPause(stack_state); compactor_.CancelIfShouldNotCompact(MarkingType::kAtomic, stack_state); } bool CppHeap::FinishConcurrentMarkingIfNeeded() { if (!TracingInitialized()) return true; return marker_->JoinConcurrentMarkingIfNeeded(); } void CppHeap::WriteBarrier(Tagged<JSObject> js_object) { DCHECK(js_object->MayHaveEmbedderFields()); DCHECK_NOT_NULL(isolate()->heap()->mark_compact_collector()); const auto descriptor = wrapper_descriptor(); const auto min_field_count = 1 + std::max(descriptor.wrappable_type_index, descriptor.wrappable_instance_index); if (js_object->GetEmbedderFieldCount() < min_field_count) return; const EmbedderDataSlot type_slot(js_object, descriptor.wrappable_type_index); const EmbedderDataSlot instance_slot(js_object, descriptor.wrappable_instance_index); isolate() ->heap() ->mark_compact_collector() ->local_marking_worklists() ->cpp_marking_state() ->MarkAndPush(type_slot, instance_slot); } namespace { void RecordEmbedderSpeed(GCTracer* tracer, base::TimeDelta marking_time, size_t marked_bytes) { constexpr auto kMinReportingTime = base::TimeDelta::FromMillisecondsD(0.5); if (marking_time > kMinReportingTime) { tracer->RecordEmbedderSpeed(marked_bytes, marking_time.InMillisecondsF()); } } } // namespace void CppHeap::TraceEpilogue() { CHECK(in_atomic_pause_); CHECK(marking_done_); #if defined(CPPGC_YOUNG_GENERATION) // Check if the young generation was enabled via flag. We must enable young // generation before calling the custom weak callbacks to make sure that the // callbacks for old objects are registered in the remembered set. if (v8_flags.cppgc_young_generation) { EnableGenerationalGC(); } #endif // defined(CPPGC_YOUNG_GENERATION) if (!TracingInitialized()) { in_atomic_pause_ = false; return; } { cppgc::subtle::DisallowGarbageCollectionScope disallow_gc_scope(*this); marker_->LeaveAtomicPause(); } marker_.reset(); if (isolate_) { used_size_ = stats_collector_->marked_bytes(); // Force a check next time increased memory is reported. This allows for // setting limits close to actual heap sizes. allocated_size_limit_for_check_ = 0; RecordEmbedderSpeed(isolate_->heap()->tracer(), stats_collector_->marking_time(), used_size_); } // The allocated bytes counter in v8 was reset to the current marked bytes, so // any pending allocated bytes updates should be discarded. buffered_allocated_bytes_ = 0; const size_t bytes_allocated_in_prefinalizers = ExecutePreFinalizers(); #if CPPGC_VERIFY_HEAP UnifiedHeapMarkingVerifier verifier(*this, *collection_type_); verifier.Run(stack_state_of_prev_gc(), stats_collector()->marked_bytes_on_current_cycle() + bytes_allocated_in_prefinalizers); #endif // CPPGC_VERIFY_HEAP USE(bytes_allocated_in_prefinalizers); #if defined(CPPGC_YOUNG_GENERATION) ResetRememberedSet(); // We can reset the remembered set on each GC because surviving Oilpan objects // are immediately considered old. ResetCrossHeapRememberedSet(); #endif // defined(CPPGC_YOUNG_GENERATION) { cppgc::subtle::NoGarbageCollectionScope no_gc(*this); cppgc::internal::SweepingConfig::CompactableSpaceHandling compactable_space_handling; { base::Optional<SweepingOnMutatorThreadForGlobalHandlesScope> global_handles_scope; if (isolate_) { global_handles_scope.emplace(*isolate_->traced_handles()); } compactable_space_handling = compactor_.CompactSpacesIfEnabled(); } const cppgc::internal::SweepingConfig sweeping_config{ SelectSweepingType(), compactable_space_handling, ShouldReduceMemory(current_gc_flags_) ? cppgc::internal::SweepingConfig::FreeMemoryHandling:: kDiscardWherePossible : cppgc::internal::SweepingConfig::FreeMemoryHandling:: kDoNotDiscard}; DCHECK_IMPLIES(!isolate_, SweepingType::kAtomic == sweeping_config.sweeping_type); sweeper().Start(sweeping_config); } in_atomic_pause_ = false; collection_type_.reset(); sweeper().NotifyDoneIfNeeded(); } void CppHeap::AllocatedObjectSizeIncreased(size_t bytes) { buffered_allocated_bytes_ += static_cast<int64_t>(bytes); ReportBufferedAllocationSizeIfPossible(); } void CppHeap::AllocatedObjectSizeDecreased(size_t bytes) { buffered_allocated_bytes_ -= static_cast<int64_t>(bytes); ReportBufferedAllocationSizeIfPossible(); } void CppHeap::ReportBufferedAllocationSizeIfPossible() { // Reporting memory to V8 may trigger GC. if (!IsGCAllowed()) { return; } // We are in attached state. DCHECK_NOT_NULL(isolate_); // The calls below may trigger full GCs that are synchronous and also execute // epilogue callbacks. Since such callbacks may allocate, the counter must // already be zeroed by that time. const int64_t bytes_to_report = buffered_allocated_bytes_; buffered_allocated_bytes_ = 0; if (bytes_to_report < 0) { DCHECK_GE(used_size_.load(std::memory_order_relaxed), bytes_to_report); used_size_.fetch_sub(static_cast<size_t>(-bytes_to_report), std::memory_order_relaxed); } else { used_size_.fetch_add(static_cast<size_t>(bytes_to_report), std::memory_order_relaxed); allocated_size_ += bytes_to_report; if (v8_flags.incremental_marking) { if (allocated_size_ > allocated_size_limit_for_check_) { Heap* heap = isolate_->heap(); heap->StartIncrementalMarkingIfAllocationLimitIsReached( heap->GCFlagsForIncrementalMarking(), kGCCallbackScheduleIdleGarbageCollection); if (heap->AllocationLimitOvershotByLargeMargin() && heap->incremental_marking()->IsMajorMarking()) { heap->FinalizeIncrementalMarkingAtomically( i::GarbageCollectionReason::kExternalFinalize); } allocated_size_limit_for_check_ = allocated_size_ + kIncrementalMarkingCheckInterval; } } } } void CppHeap::CollectGarbageForTesting(CollectionType collection_type, StackState stack_state) { if (!IsDetachedGCAllowed()) { return; } // Finish sweeping in case it is still running. sweeper().FinishIfRunning(); if (isolate_) { reinterpret_cast<v8::Isolate*>(isolate_) ->RequestGarbageCollectionForTesting( v8::Isolate::kFullGarbageCollection, stack_state); return; } stack()->SetMarkerIfNeededAndCallback([this, collection_type, stack_state]() { // Perform an atomic GC, with starting incremental/concurrent marking and // immediately finalizing the garbage collection. if (!IsMarking()) { InitializeTracing(collection_type, GarbageCollectionFlagValues::kForced); StartTracing(); } EnterFinalPause(stack_state); CHECK(AdvanceTracing(v8::base::TimeDelta::Max())); if (FinishConcurrentMarkingIfNeeded()) { CHECK(AdvanceTracing(v8::base::TimeDelta::Max())); } TraceEpilogue(); }); } void CppHeap::EnableDetachedGarbageCollectionsForTesting() { CHECK(!in_detached_testing_mode_); CHECK_NULL(isolate_); no_gc_scope_--; in_detached_testing_mode_ = true; static_cast<CppgcPlatformAdapter*>(platform()) ->EnableDetachedModeForTesting(); } void CppHeap::StartIncrementalGarbageCollectionForTesting() { DCHECK(!in_no_gc_scope()); DCHECK_NULL(isolate_); if (IsMarking()) return; force_incremental_marking_for_testing_ = true; InitializeTracing(CollectionType::kMajor, GarbageCollectionFlagValues::kForced); StartTracing(); force_incremental_marking_for_testing_ = false; } void CppHeap::FinalizeIncrementalGarbageCollectionForTesting( cppgc::EmbedderStackState stack_state) { DCHECK(!in_no_gc_scope()); DCHECK_NULL(isolate_); DCHECK(IsMarking()); if (IsMarking()) { CollectGarbageForTesting(CollectionType::kMajor, stack_state); } sweeper_.FinishIfRunning(); } namespace { void ReportCustomSpaceStatistics( cppgc::internal::RawHeap& raw_heap, std::vector<cppgc::CustomSpaceIndex> custom_spaces, std::unique_ptr<CustomSpaceStatisticsReceiver> receiver) { for (auto custom_space_index : custom_spaces) { const cppgc::internal::BaseSpace* space = raw_heap.CustomSpace(custom_space_index); size_t allocated_bytes = std::accumulate( space->begin(), space->end(), 0, [](size_t sum, auto* page) { return sum + page->AllocatedBytesAtLastGC(); }); receiver->AllocatedBytes(custom_space_index, allocated_bytes); } } class CollectCustomSpaceStatisticsAtLastGCTask final : public v8::Task { public: static constexpr v8::base::TimeDelta kTaskDelayMs = v8::base::TimeDelta::FromMilliseconds(10); CollectCustomSpaceStatisticsAtLastGCTask( cppgc::internal::HeapBase& heap, std::vector<cppgc::CustomSpaceIndex> custom_spaces, std::unique_ptr<CustomSpaceStatisticsReceiver> receiver) : heap_(heap), custom_spaces_(std::move(custom_spaces)), receiver_(std::move(receiver)) {} void Run() final { cppgc::internal::Sweeper& sweeper = heap_.sweeper(); if (sweeper.PerformSweepOnMutatorThread( kStepSizeMs, cppgc::internal::StatsCollector::kSweepInTaskForStatistics)) { // Sweeping is done. DCHECK(!sweeper.IsSweepingInProgress()); ReportCustomSpaceStatistics(heap_.raw_heap(), std::move(custom_spaces_), std::move(receiver_)); } else { heap_.platform()->GetForegroundTaskRunner()->PostDelayedTask( std::make_unique<CollectCustomSpaceStatisticsAtLastGCTask>( heap_, std::move(custom_spaces_), std::move(receiver_)), kTaskDelayMs.InSecondsF()); } } private: static constexpr v8::base::TimeDelta kStepSizeMs = v8::base::TimeDelta::FromMilliseconds(5); cppgc::internal::HeapBase& heap_; std::vector<cppgc::CustomSpaceIndex> custom_spaces_; std::unique_ptr<CustomSpaceStatisticsReceiver> receiver_; }; constexpr v8::base::TimeDelta CollectCustomSpaceStatisticsAtLastGCTask::kTaskDelayMs; constexpr v8::base::TimeDelta CollectCustomSpaceStatisticsAtLastGCTask::kStepSizeMs; } // namespace void CppHeap::CollectCustomSpaceStatisticsAtLastGC( std::vector<cppgc::CustomSpaceIndex> custom_spaces, std::unique_ptr<CustomSpaceStatisticsReceiver> receiver) { if (sweeper().IsSweepingInProgress()) { platform()->GetForegroundTaskRunner()->PostDelayedTask( std::make_unique<CollectCustomSpaceStatisticsAtLastGCTask>( AsBase(), std::move(custom_spaces), std::move(receiver)), CollectCustomSpaceStatisticsAtLastGCTask::kTaskDelayMs.InSecondsF()); return; } ReportCustomSpaceStatistics(raw_heap(), std::move(custom_spaces), std::move(receiver)); } CppHeap::MetricRecorderAdapter* CppHeap::GetMetricRecorder() const { return static_cast<MetricRecorderAdapter*>( stats_collector_->GetMetricRecorder()); } void CppHeap::FinishSweepingIfRunning() { sweeper_.FinishIfRunning(); if (isolate_) { isolate_->traced_handles()->DeleteEmptyBlocks(); } } void CppHeap::FinishSweepingIfOutOfWork() { sweeper_.FinishIfOutOfWork(); } std::unique_ptr<CppMarkingState> CppHeap::CreateCppMarkingState() { if (!TracingInitialized()) return {}; DCHECK(IsMarking()); return std::make_unique<CppMarkingState>( isolate(), wrapper_descriptor_, std::make_unique<cppgc::internal::MarkingStateBase>( AsBase(), marker()->To<UnifiedHeapMarker>().GetMarkingWorklists())); } std::unique_ptr<CppMarkingState> CppHeap::CreateCppMarkingStateForMutatorThread() { if (!TracingInitialized()) return {}; DCHECK(IsMarking()); return std::make_unique<CppMarkingState>( isolate(), wrapper_descriptor_, marker()->To<UnifiedHeapMarker>().GetMutatorMarkingState()); } CppHeap::PauseConcurrentMarkingScope::PauseConcurrentMarkingScope( CppHeap* cpp_heap) { if (cpp_heap && cpp_heap->marker()) { pause_scope_.emplace(*cpp_heap->marker()); } } void CppHeap::CollectGarbage(cppgc::internal::GCConfig config) { if (!IsGCAllowed()) { return; } // TODO(mlippautz): Respect full config. const auto flags = (config.free_memory_handling == cppgc::internal::GCConfig::FreeMemoryHandling::kDiscardWherePossible) ? GCFlag::kReduceMemoryFootprint : GCFlag::kNoFlags; isolate_->heap()->CollectAllGarbage( flags, GarbageCollectionReason::kCppHeapAllocationFailure); } const cppgc::EmbedderStackState* CppHeap::override_stack_state() const { return HeapBase::override_stack_state(); } void CppHeap::StartIncrementalGarbageCollection(cppgc::internal::GCConfig) { UNIMPLEMENTED(); } size_t CppHeap::epoch() const { UNIMPLEMENTED(); } void CppHeap::ResetCrossHeapRememberedSet() { if (!generational_gc_supported()) { DCHECK(cross_heap_remembered_set_.IsEmpty()); return; } DCHECK(isolate_); cross_heap_remembered_set_.Reset(*isolate_); } void CppHeap::UpdateGCCapabilitiesFromFlagsForTesting() { UpdateGCCapabilitiesFromFlags(); } bool CppHeap::IsDetachedGCAllowed() const { return (isolate_ || in_detached_testing_mode_) && HeapBase::IsGCAllowed(); } bool CppHeap::IsGCAllowed() const { return isolate_ && HeapBase::IsGCAllowed(); } } // namespace internal } // namespace v8