| // Copyright 2017 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/concurrent-marking.h" |
| |
| #include <stack> |
| #include <unordered_map> |
| |
| #include "include/v8config.h" |
| #include "src/common/globals.h" |
| #include "src/execution/isolate.h" |
| #include "src/heap/gc-tracer-inl.h" |
| #include "src/heap/gc-tracer.h" |
| #include "src/heap/heap-inl.h" |
| #include "src/heap/heap.h" |
| #include "src/heap/mark-compact-inl.h" |
| #include "src/heap/mark-compact.h" |
| #include "src/heap/marking-state-inl.h" |
| #include "src/heap/marking-visitor-inl.h" |
| #include "src/heap/marking-visitor.h" |
| #include "src/heap/marking.h" |
| #include "src/heap/memory-chunk.h" |
| #include "src/heap/memory-measurement-inl.h" |
| #include "src/heap/memory-measurement.h" |
| #include "src/heap/object-lock.h" |
| #include "src/heap/objects-visiting-inl.h" |
| #include "src/heap/objects-visiting.h" |
| #include "src/heap/weak-object-worklists.h" |
| #include "src/init/v8.h" |
| #include "src/objects/data-handler-inl.h" |
| #include "src/objects/embedder-data-array-inl.h" |
| #include "src/objects/hash-table-inl.h" |
| #include "src/objects/js-array-buffer-inl.h" |
| #include "src/objects/slots-inl.h" |
| #include "src/objects/transitions-inl.h" |
| #include "src/objects/visitors.h" |
| #include "src/utils/utils-inl.h" |
| #include "src/utils/utils.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| class ConcurrentMarkingState final |
| : public MarkingStateBase<ConcurrentMarkingState, AccessMode::ATOMIC> { |
| public: |
| ConcurrentMarkingState(PtrComprCageBase cage_base, |
| MemoryChunkDataMap* memory_chunk_data) |
| : MarkingStateBase(cage_base), memory_chunk_data_(memory_chunk_data) {} |
| |
| MarkingBitmap* bitmap(MemoryChunk* chunk) const { |
| return chunk->marking_bitmap(); |
| } |
| |
| void IncrementLiveBytes(MemoryChunk* chunk, intptr_t by) { |
| DCHECK_IMPLIES(V8_COMPRESS_POINTERS_8GB_BOOL, |
| IsAligned(by, kObjectAlignment8GbHeap)); |
| (*memory_chunk_data_)[chunk].live_bytes += by; |
| } |
| |
| // The live_bytes and SetLiveBytes methods of the marking state are |
| // not used by the concurrent marker. |
| |
| private: |
| MemoryChunkDataMap* memory_chunk_data_; |
| }; |
| |
| class YoungGenerationConcurrentMarkingVisitor final |
| : public YoungGenerationMarkingVisitorBase< |
| YoungGenerationConcurrentMarkingVisitor, ConcurrentMarkingState> { |
| public: |
| YoungGenerationConcurrentMarkingVisitor( |
| Heap* heap, MarkingWorklists::Local* worklists_local, |
| MemoryChunkDataMap* memory_chunk_data) |
| : YoungGenerationMarkingVisitorBase< |
| YoungGenerationConcurrentMarkingVisitor, ConcurrentMarkingState>( |
| heap->isolate(), worklists_local), |
| marking_state_(heap->isolate(), memory_chunk_data) {} |
| |
| using YoungGenerationMarkingVisitorBase< |
| YoungGenerationConcurrentMarkingVisitor, |
| ConcurrentMarkingState>::VisitMapPointerIfNeeded; |
| |
| static constexpr bool EnableConcurrentVisitation() { return true; } |
| |
| template <typename TSlot> |
| void RecordSlot(HeapObject object, TSlot slot, HeapObject target) {} |
| |
| ConcurrentMarkingState* marking_state() { return &marking_state_; } |
| |
| template <typename TSlot> |
| V8_INLINE void VisitPointersImpl(HeapObject host, TSlot start, TSlot end) { |
| for (TSlot slot = start; slot < end; ++slot) { |
| typename TSlot::TObject target = |
| slot.Relaxed_Load(ObjectVisitorWithCageBases::cage_base()); |
| VisitObjectImpl(target); |
| } |
| } |
| |
| private: |
| ConcurrentMarkingState marking_state_; |
| }; |
| |
| class ConcurrentMarkingVisitor final |
| : public MarkingVisitorBase<ConcurrentMarkingVisitor, |
| ConcurrentMarkingState> { |
| public: |
| ConcurrentMarkingVisitor(int task_id, |
| MarkingWorklists::Local* local_marking_worklists, |
| WeakObjects::Local* local_weak_objects, Heap* heap, |
| unsigned mark_compact_epoch, |
| base::EnumSet<CodeFlushMode> code_flush_mode, |
| bool embedder_tracing_enabled, |
| bool should_keep_ages_unchanged, |
| MemoryChunkDataMap* memory_chunk_data) |
| : MarkingVisitorBase(local_marking_worklists, local_weak_objects, heap, |
| mark_compact_epoch, code_flush_mode, |
| embedder_tracing_enabled, |
| should_keep_ages_unchanged), |
| marking_state_(heap->isolate(), memory_chunk_data), |
| memory_chunk_data_(memory_chunk_data) {} |
| |
| using MarkingVisitorBase<ConcurrentMarkingVisitor, |
| ConcurrentMarkingState>::VisitMapPointerIfNeeded; |
| |
| static constexpr bool EnableConcurrentVisitation() { return true; } |
| |
| template <typename T> |
| static V8_INLINE T Cast(HeapObject object) { |
| return T::cast(object); |
| } |
| |
| // Implements ephemeron semantics: Marks value if key is already reachable. |
| // Returns true if value was actually marked. |
| bool ProcessEphemeron(HeapObject key, HeapObject value) { |
| if (marking_state_.IsMarked(key)) { |
| if (marking_state_.TryMark(value)) { |
| local_marking_worklists_->Push(value); |
| return true; |
| } |
| |
| } else if (marking_state_.IsUnmarked(value)) { |
| local_weak_objects_->next_ephemerons_local.Push(Ephemeron{key, value}); |
| } |
| return false; |
| } |
| |
| template <typename TSlot> |
| void RecordSlot(HeapObject object, TSlot slot, HeapObject target) { |
| MarkCompactCollector::RecordSlot(object, slot, target); |
| } |
| |
| ConcurrentMarkingState* marking_state() { return &marking_state_; } |
| |
| private: |
| void RecordRelocSlot(InstructionStream host, RelocInfo* rinfo, |
| HeapObject target) { |
| if (!MarkCompactCollector::ShouldRecordRelocSlot(host, rinfo, target)) { |
| return; |
| } |
| |
| MarkCompactCollector::RecordRelocSlotInfo info = |
| MarkCompactCollector::ProcessRelocInfo(host, rinfo, target); |
| |
| MemoryChunkData& data = (*memory_chunk_data_)[info.memory_chunk]; |
| if (!data.typed_slots) { |
| data.typed_slots.reset(new TypedSlots()); |
| } |
| data.typed_slots->Insert(info.slot_type, info.offset); |
| } |
| |
| TraceRetainingPathMode retaining_path_mode() { |
| return TraceRetainingPathMode::kDisabled; |
| } |
| |
| ConcurrentMarkingState marking_state_; |
| MemoryChunkDataMap* memory_chunk_data_; |
| |
| friend class MarkingVisitorBase<ConcurrentMarkingVisitor, |
| ConcurrentMarkingState>; |
| }; |
| |
| // The Deserializer changes the map from StrongDescriptorArray to |
| // DescriptorArray |
| template <> |
| StrongDescriptorArray ConcurrentMarkingVisitor::Cast(HeapObject object) { |
| return StrongDescriptorArray::unchecked_cast(DescriptorArray::cast(object)); |
| } |
| |
| class ConcurrentMarking::JobTaskMajor : public v8::JobTask { |
| public: |
| JobTaskMajor(ConcurrentMarking* concurrent_marking, |
| unsigned mark_compact_epoch, |
| base::EnumSet<CodeFlushMode> code_flush_mode, |
| bool should_keep_ages_unchanged) |
| : concurrent_marking_(concurrent_marking), |
| mark_compact_epoch_(mark_compact_epoch), |
| code_flush_mode_(code_flush_mode), |
| should_keep_ages_unchanged_(should_keep_ages_unchanged) {} |
| |
| ~JobTaskMajor() override = default; |
| JobTaskMajor(const JobTaskMajor&) = delete; |
| JobTaskMajor& operator=(const JobTaskMajor&) = delete; |
| |
| // v8::JobTask overrides. |
| void Run(JobDelegate* delegate) override { |
| if (delegate->IsJoiningThread()) { |
| // TRACE_GC is not needed here because the caller opens the right scope. |
| concurrent_marking_->RunMajor(delegate, code_flush_mode_, |
| mark_compact_epoch_, |
| should_keep_ages_unchanged_); |
| } else { |
| TRACE_GC_EPOCH(concurrent_marking_->heap_->tracer(), |
| GCTracer::Scope::MC_BACKGROUND_MARKING, |
| ThreadKind::kBackground); |
| concurrent_marking_->RunMajor(delegate, code_flush_mode_, |
| mark_compact_epoch_, |
| should_keep_ages_unchanged_); |
| } |
| } |
| |
| size_t GetMaxConcurrency(size_t worker_count) const override { |
| return concurrent_marking_->GetMaxConcurrency(worker_count); |
| } |
| |
| private: |
| ConcurrentMarking* concurrent_marking_; |
| const unsigned mark_compact_epoch_; |
| base::EnumSet<CodeFlushMode> code_flush_mode_; |
| const bool should_keep_ages_unchanged_; |
| }; |
| |
| class ConcurrentMarking::JobTaskMinor : public v8::JobTask { |
| public: |
| explicit JobTaskMinor(ConcurrentMarking* concurrent_marking) |
| : concurrent_marking_(concurrent_marking) {} |
| |
| ~JobTaskMinor() override = default; |
| JobTaskMinor(const JobTaskMinor&) = delete; |
| JobTaskMinor& operator=(const JobTaskMinor&) = delete; |
| |
| // v8::JobTask overrides. |
| void Run(JobDelegate* delegate) override { |
| if (delegate->IsJoiningThread()) { |
| // TRACE_GC is not needed here because the caller opens the right scope. |
| concurrent_marking_->RunMinor(delegate); |
| } else { |
| TRACE_GC_EPOCH(concurrent_marking_->heap_->tracer(), |
| GCTracer::Scope::MINOR_MC_BACKGROUND_MARKING, |
| ThreadKind::kBackground); |
| concurrent_marking_->RunMinor(delegate); |
| } |
| } |
| |
| size_t GetMaxConcurrency(size_t worker_count) const override { |
| return concurrent_marking_->GetMaxConcurrency(worker_count); |
| } |
| |
| private: |
| ConcurrentMarking* concurrent_marking_; |
| }; |
| |
| ConcurrentMarking::ConcurrentMarking(Heap* heap, WeakObjects* weak_objects) |
| : heap_(heap), weak_objects_(weak_objects) { |
| #ifndef V8_ATOMIC_OBJECT_FIELD_WRITES |
| // Concurrent marking requires atomic object field writes. |
| CHECK(!v8_flags.concurrent_marking); |
| #endif |
| int max_tasks; |
| if (v8_flags.concurrent_marking_max_worker_num == 0) { |
| max_tasks = V8::GetCurrentPlatform()->NumberOfWorkerThreads(); |
| } else { |
| max_tasks = v8_flags.concurrent_marking_max_worker_num; |
| } |
| |
| task_state_.reserve(max_tasks + 1); |
| for (int i = 0; i <= max_tasks; ++i) { |
| task_state_.emplace_back(std::make_unique<TaskState>()); |
| } |
| } |
| |
| void ConcurrentMarking::RunMajor(JobDelegate* delegate, |
| base::EnumSet<CodeFlushMode> code_flush_mode, |
| unsigned mark_compact_epoch, |
| bool should_keep_ages_unchanged) { |
| RwxMemoryWriteScope::SetDefaultPermissionsForNewThread(); |
| size_t kBytesUntilInterruptCheck = 64 * KB; |
| int kObjectsUntilInterruptCheck = 1000; |
| uint8_t task_id = delegate->GetTaskId() + 1; |
| TaskState* task_state = task_state_[task_id].get(); |
| auto* cpp_heap = CppHeap::From(heap_->cpp_heap()); |
| MarkingWorklists::Local local_marking_worklists( |
| marking_worklists_, cpp_heap |
| ? cpp_heap->CreateCppMarkingState() |
| : MarkingWorklists::Local::kNoCppMarkingState); |
| WeakObjects::Local local_weak_objects(weak_objects_); |
| ConcurrentMarkingVisitor visitor( |
| task_id, &local_marking_worklists, &local_weak_objects, heap_, |
| mark_compact_epoch, code_flush_mode, heap_->cpp_heap(), |
| should_keep_ages_unchanged, &task_state->memory_chunk_data); |
| NativeContextInferrer& native_context_inferrer = |
| task_state->native_context_inferrer; |
| NativeContextStats& native_context_stats = task_state->native_context_stats; |
| double time_ms; |
| size_t marked_bytes = 0; |
| Isolate* isolate = heap_->isolate(); |
| if (v8_flags.trace_concurrent_marking) { |
| isolate->PrintWithTimestamp("Starting major concurrent marking task %d\n", |
| task_id); |
| } |
| bool another_ephemeron_iteration = false; |
| |
| { |
| TimedScope scope(&time_ms); |
| |
| { |
| Ephemeron ephemeron; |
| while (local_weak_objects.current_ephemerons_local.Pop(&ephemeron)) { |
| if (visitor.ProcessEphemeron(ephemeron.key, ephemeron.value)) { |
| another_ephemeron_iteration = true; |
| } |
| } |
| } |
| bool is_per_context_mode = local_marking_worklists.IsPerContextMode(); |
| bool done = false; |
| CodePageHeaderModificationScope rwx_write_scope( |
| "Marking a InstructionStream object requires write access to the " |
| "Code page header"); |
| while (!done) { |
| size_t current_marked_bytes = 0; |
| int objects_processed = 0; |
| while (current_marked_bytes < kBytesUntilInterruptCheck && |
| objects_processed < kObjectsUntilInterruptCheck) { |
| HeapObject object; |
| if (!local_marking_worklists.Pop(&object)) { |
| done = true; |
| break; |
| } |
| DCHECK(!object.InReadOnlySpace()); |
| objects_processed++; |
| |
| Address new_space_top = kNullAddress; |
| Address new_space_limit = kNullAddress; |
| Address new_large_object = kNullAddress; |
| |
| if (heap_->new_space()) { |
| // The order of the two loads is important. |
| new_space_top = heap_->new_space()->original_top_acquire(); |
| new_space_limit = heap_->new_space()->original_limit_relaxed(); |
| } |
| |
| if (heap_->new_lo_space()) { |
| new_large_object = heap_->new_lo_space()->pending_object(); |
| } |
| |
| Address addr = object.address(); |
| |
| if ((new_space_top <= addr && addr < new_space_limit) || |
| addr == new_large_object) { |
| local_marking_worklists.PushOnHold(object); |
| } else { |
| Map map = object.map(isolate, kAcquireLoad); |
| if (is_per_context_mode) { |
| Address context; |
| if (native_context_inferrer.Infer(isolate, map, object, &context)) { |
| local_marking_worklists.SwitchToContext(context); |
| } |
| } |
| const auto visited_size = visitor.Visit(map, object); |
| visitor.marking_state()->IncrementLiveBytes( |
| MemoryChunk::cast(BasicMemoryChunk::FromHeapObject(object)), |
| ALIGN_TO_ALLOCATION_ALIGNMENT(visited_size)); |
| if (is_per_context_mode) { |
| native_context_stats.IncrementSize( |
| local_marking_worklists.Context(), map, object, visited_size); |
| } |
| current_marked_bytes += visited_size; |
| } |
| } |
| if (objects_processed > 0) another_ephemeron_iteration = true; |
| marked_bytes += current_marked_bytes; |
| base::AsAtomicWord::Relaxed_Store<size_t>(&task_state->marked_bytes, |
| marked_bytes); |
| if (delegate->ShouldYield()) { |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.gc"), |
| "ConcurrentMarking::RunMajor Preempted"); |
| break; |
| } |
| } |
| |
| if (done) { |
| Ephemeron ephemeron; |
| while (local_weak_objects.discovered_ephemerons_local.Pop(&ephemeron)) { |
| if (visitor.ProcessEphemeron(ephemeron.key, ephemeron.value)) { |
| another_ephemeron_iteration = true; |
| } |
| } |
| } |
| |
| local_marking_worklists.Publish(); |
| local_weak_objects.Publish(); |
| base::AsAtomicWord::Relaxed_Store<size_t>(&task_state->marked_bytes, 0); |
| total_marked_bytes_ += marked_bytes; |
| |
| if (another_ephemeron_iteration) { |
| set_another_ephemeron_iteration(true); |
| } |
| } |
| if (v8_flags.trace_concurrent_marking) { |
| heap_->isolate()->PrintWithTimestamp( |
| "Major task %d concurrently marked %dKB in %.2fms\n", task_id, |
| static_cast<int>(marked_bytes / KB), time_ms); |
| } |
| } |
| |
| void ConcurrentMarking::RunMinor(JobDelegate* delegate) { |
| RwxMemoryWriteScope::SetDefaultPermissionsForNewThread(); |
| size_t kBytesUntilInterruptCheck = 64 * KB; |
| int kObjectsUntilInterruptCheck = 1000; |
| uint8_t task_id = delegate->GetTaskId() + 1; |
| TaskState* task_state = task_state_[task_id].get(); |
| MarkingWorklists::Local local_marking_worklists( |
| marking_worklists_, MarkingWorklists::Local::kNoCppMarkingState); |
| YoungGenerationConcurrentMarkingVisitor visitor( |
| heap_, &local_marking_worklists, &task_state->memory_chunk_data); |
| double time_ms; |
| size_t marked_bytes = 0; |
| Isolate* isolate = heap_->isolate(); |
| if (v8_flags.trace_concurrent_marking) { |
| isolate->PrintWithTimestamp("Starting minor concurrent marking task %d\n", |
| task_id); |
| } |
| |
| { |
| TimedScope scope(&time_ms); |
| bool done = false; |
| CodePageHeaderModificationScope rwx_write_scope( |
| "Marking a InstructionStream object requires write access to the " |
| "Code page header"); |
| while (!done) { |
| size_t current_marked_bytes = 0; |
| int objects_processed = 0; |
| while (current_marked_bytes < kBytesUntilInterruptCheck && |
| objects_processed < kObjectsUntilInterruptCheck) { |
| HeapObject object; |
| if (!local_marking_worklists.Pop(&object)) { |
| done = true; |
| break; |
| } |
| objects_processed++; |
| |
| Address new_space_top = kNullAddress; |
| Address new_space_limit = kNullAddress; |
| Address new_large_object = kNullAddress; |
| |
| if (heap_->new_space()) { |
| // The order of the two loads is important. |
| new_space_top = heap_->new_space()->original_top_acquire(); |
| new_space_limit = heap_->new_space()->original_limit_relaxed(); |
| } |
| |
| if (heap_->new_lo_space()) { |
| new_large_object = heap_->new_lo_space()->pending_object(); |
| } |
| |
| Address addr = object.address(); |
| |
| if ((new_space_top <= addr && addr < new_space_limit) || |
| addr == new_large_object) { |
| local_marking_worklists.PushOnHold(object); |
| } else { |
| Map map = object.map(isolate, kAcquireLoad); |
| const auto visited_size = visitor.Visit(map, object); |
| current_marked_bytes += visited_size; |
| if (visited_size) { |
| visitor.marking_state()->IncrementLiveBytes( |
| MemoryChunk::cast(BasicMemoryChunk::FromHeapObject(object)), |
| ALIGN_TO_ALLOCATION_ALIGNMENT(visited_size)); |
| } |
| } |
| } |
| marked_bytes += current_marked_bytes; |
| base::AsAtomicWord::Relaxed_Store<size_t>(&task_state->marked_bytes, |
| marked_bytes); |
| if (delegate->ShouldYield()) { |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.gc"), |
| "ConcurrentMarking::RunMinor Preempted"); |
| break; |
| } |
| } |
| |
| local_marking_worklists.Publish(); |
| base::AsAtomicWord::Relaxed_Store<size_t>(&task_state->marked_bytes, 0); |
| total_marked_bytes_ += marked_bytes; |
| } |
| if (v8_flags.trace_concurrent_marking) { |
| heap_->isolate()->PrintWithTimestamp( |
| "Minor task %d concurrently marked %dKB in %.2fms\n", task_id, |
| static_cast<int>(marked_bytes / KB), time_ms); |
| } |
| } |
| |
| size_t ConcurrentMarking::GetMaxConcurrency(size_t worker_count) { |
| size_t marking_items = marking_worklists_->shared()->Size(); |
| marking_items += marking_worklists_->other()->Size(); |
| for (auto& worklist : marking_worklists_->context_worklists()) |
| marking_items += worklist.worklist->Size(); |
| return std::min<size_t>( |
| task_state_.size() - 1, |
| worker_count + |
| std::max<size_t>({marking_items, |
| weak_objects_->discovered_ephemerons.Size(), |
| weak_objects_->current_ephemerons.Size()})); |
| } |
| |
| void ConcurrentMarking::ScheduleJob(GarbageCollector garbage_collector, |
| TaskPriority priority) { |
| DCHECK(v8_flags.parallel_marking || v8_flags.concurrent_marking); |
| DCHECK(!heap_->IsTearingDown()); |
| DCHECK(IsStopped()); |
| |
| garbage_collector_ = garbage_collector; |
| if (garbage_collector == GarbageCollector::MARK_COMPACTOR) { |
| marking_worklists_ = heap_->mark_compact_collector()->marking_worklists(); |
| job_handle_ = V8::GetCurrentPlatform()->PostJob( |
| priority, std::make_unique<JobTaskMajor>( |
| this, heap_->mark_compact_collector()->epoch(), |
| heap_->mark_compact_collector()->code_flush_mode(), |
| heap_->ShouldCurrentGCKeepAgesUnchanged())); |
| } else { |
| DCHECK(garbage_collector == GarbageCollector::MINOR_MARK_COMPACTOR); |
| marking_worklists_ = |
| heap_->minor_mark_compact_collector()->marking_worklists(); |
| job_handle_ = V8::GetCurrentPlatform()->PostJob( |
| priority, std::make_unique<JobTaskMinor>(this)); |
| } |
| DCHECK(job_handle_->IsValid()); |
| } |
| |
| bool ConcurrentMarking::IsWorkLeft() { |
| return !marking_worklists_->shared()->IsEmpty() || |
| !weak_objects_->current_ephemerons.IsEmpty() || |
| !weak_objects_->discovered_ephemerons.IsEmpty(); |
| } |
| |
| void ConcurrentMarking::RescheduleJobIfNeeded( |
| GarbageCollector garbage_collector, TaskPriority priority) { |
| DCHECK(v8_flags.parallel_marking || v8_flags.concurrent_marking); |
| if (heap_->IsTearingDown()) return; |
| |
| if (IsStopped()) { |
| // This DCHECK is for the case that concurrent marking was paused. |
| DCHECK_IMPLIES(garbage_collector_.has_value(), |
| garbage_collector == garbage_collector_); |
| ScheduleJob(garbage_collector, priority); |
| } else { |
| DCHECK_EQ(garbage_collector, garbage_collector_); |
| if (!IsWorkLeft()) return; |
| if (priority != TaskPriority::kUserVisible) |
| job_handle_->UpdatePriority(priority); |
| job_handle_->NotifyConcurrencyIncrease(); |
| } |
| } |
| |
| void ConcurrentMarking::Join() { |
| DCHECK(v8_flags.parallel_marking || v8_flags.concurrent_marking); |
| if (!job_handle_ || !job_handle_->IsValid()) return; |
| job_handle_->Join(); |
| garbage_collector_.reset(); |
| } |
| |
| bool ConcurrentMarking::Pause() { |
| DCHECK(v8_flags.parallel_marking || v8_flags.concurrent_marking); |
| if (!job_handle_ || !job_handle_->IsValid()) return false; |
| |
| job_handle_->Cancel(); |
| return true; |
| } |
| |
| void ConcurrentMarking::Cancel() { |
| Pause(); |
| garbage_collector_.reset(); |
| } |
| |
| bool ConcurrentMarking::IsStopped() { |
| if (!v8_flags.concurrent_marking && !v8_flags.parallel_marking) return true; |
| |
| return !job_handle_ || !job_handle_->IsValid(); |
| } |
| |
| void ConcurrentMarking::Resume() { |
| DCHECK(garbage_collector_.has_value()); |
| RescheduleJobIfNeeded(garbage_collector_.value()); |
| } |
| |
| void ConcurrentMarking::FlushNativeContexts(NativeContextStats* main_stats) { |
| DCHECK(!job_handle_ || !job_handle_->IsValid()); |
| for (size_t i = 1; i < task_state_.size(); i++) { |
| main_stats->Merge(task_state_[i]->native_context_stats); |
| task_state_[i]->native_context_stats.Clear(); |
| } |
| } |
| |
| void ConcurrentMarking::FlushMemoryChunkData( |
| NonAtomicMarkingState* marking_state) { |
| DCHECK(!job_handle_ || !job_handle_->IsValid()); |
| for (size_t i = 1; i < task_state_.size(); i++) { |
| MemoryChunkDataMap& memory_chunk_data = task_state_[i]->memory_chunk_data; |
| for (auto& pair : memory_chunk_data) { |
| // ClearLiveness sets the live bytes to zero. |
| // Pages with zero live bytes might be already unmapped. |
| MemoryChunk* memory_chunk = pair.first; |
| MemoryChunkData& data = pair.second; |
| if (data.live_bytes) { |
| marking_state->IncrementLiveBytes(memory_chunk, data.live_bytes); |
| } |
| if (data.typed_slots) { |
| RememberedSet<OLD_TO_OLD>::MergeTyped(memory_chunk, |
| std::move(data.typed_slots)); |
| } |
| } |
| memory_chunk_data.clear(); |
| task_state_[i]->marked_bytes = 0; |
| } |
| total_marked_bytes_ = 0; |
| } |
| |
| void ConcurrentMarking::ClearMemoryChunkData(MemoryChunk* chunk) { |
| DCHECK(!job_handle_ || !job_handle_->IsValid()); |
| for (size_t i = 1; i < task_state_.size(); i++) { |
| auto it = task_state_[i]->memory_chunk_data.find(chunk); |
| if (it != task_state_[i]->memory_chunk_data.end()) { |
| it->second.live_bytes = 0; |
| it->second.typed_slots.reset(); |
| } |
| } |
| } |
| |
| size_t ConcurrentMarking::TotalMarkedBytes() { |
| size_t result = 0; |
| for (size_t i = 1; i < task_state_.size(); i++) { |
| result += |
| base::AsAtomicWord::Relaxed_Load<size_t>(&task_state_[i]->marked_bytes); |
| } |
| result += total_marked_bytes_; |
| return result; |
| } |
| |
| ConcurrentMarking::PauseScope::PauseScope(ConcurrentMarking* concurrent_marking) |
| : concurrent_marking_(concurrent_marking), |
| resume_on_exit_(v8_flags.concurrent_marking && |
| concurrent_marking_->Pause()) { |
| DCHECK_IMPLIES(resume_on_exit_, v8_flags.concurrent_marking); |
| } |
| |
| ConcurrentMarking::PauseScope::~PauseScope() { |
| if (resume_on_exit_) { |
| DCHECK_EQ(concurrent_marking_->garbage_collector_, |
| GarbageCollector::MARK_COMPACTOR); |
| concurrent_marking_->Resume(); |
| } |
| } |
| |
| } // namespace internal |
| } // namespace v8 |