| // Copyright 2012 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/mark-compact.h" |
| |
| #include <memory> |
| #include <unordered_map> |
| #include <unordered_set> |
| |
| #include "src/base/logging.h" |
| #include "src/base/optional.h" |
| #include "src/base/platform/mutex.h" |
| #include "src/base/platform/platform.h" |
| #include "src/base/utils/random-number-generator.h" |
| #include "src/base/v8-fallthrough.h" |
| #include "src/codegen/compilation-cache.h" |
| #include "src/common/globals.h" |
| #include "src/deoptimizer/deoptimizer.h" |
| #include "src/execution/execution.h" |
| #include "src/execution/frames-inl.h" |
| #include "src/execution/isolate-utils-inl.h" |
| #include "src/execution/isolate-utils.h" |
| #include "src/execution/vm-state-inl.h" |
| #include "src/flags/flags.h" |
| #include "src/handles/global-handles.h" |
| #include "src/heap/array-buffer-sweeper.h" |
| #include "src/heap/basic-memory-chunk.h" |
| #include "src/heap/code-object-registry.h" |
| #include "src/heap/concurrent-allocator.h" |
| #include "src/heap/evacuation-allocator-inl.h" |
| #include "src/heap/evacuation-verifier-inl.h" |
| #include "src/heap/gc-tracer-inl.h" |
| #include "src/heap/gc-tracer.h" |
| #include "src/heap/heap.h" |
| #include "src/heap/incremental-marking-inl.h" |
| #include "src/heap/index-generator.h" |
| #include "src/heap/invalidated-slots-inl.h" |
| #include "src/heap/invalidated-slots.h" |
| #include "src/heap/large-spaces.h" |
| #include "src/heap/mark-compact-inl.h" |
| #include "src/heap/marking-barrier.h" |
| #include "src/heap/marking-inl.h" |
| #include "src/heap/marking-state-inl.h" |
| #include "src/heap/marking-visitor-inl.h" |
| #include "src/heap/marking-visitor.h" |
| #include "src/heap/memory-chunk-layout.h" |
| #include "src/heap/memory-chunk.h" |
| #include "src/heap/memory-measurement-inl.h" |
| #include "src/heap/memory-measurement.h" |
| #include "src/heap/new-spaces.h" |
| #include "src/heap/object-stats.h" |
| #include "src/heap/objects-visiting-inl.h" |
| #include "src/heap/parallel-work-item.h" |
| #include "src/heap/pretenuring-handler-inl.h" |
| #include "src/heap/pretenuring-handler.h" |
| #include "src/heap/read-only-heap.h" |
| #include "src/heap/read-only-spaces.h" |
| #include "src/heap/remembered-set.h" |
| #include "src/heap/safepoint.h" |
| #include "src/heap/slot-set.h" |
| #include "src/heap/spaces-inl.h" |
| #include "src/heap/sweeper.h" |
| #include "src/heap/traced-handles-marking-visitor.h" |
| #include "src/heap/weak-object-worklists.h" |
| #include "src/init/v8.h" |
| #include "src/logging/tracing-flags.h" |
| #include "src/objects/embedder-data-array-inl.h" |
| #include "src/objects/foreign.h" |
| #include "src/objects/hash-table-inl.h" |
| #include "src/objects/heap-object-inl.h" |
| #include "src/objects/instance-type.h" |
| #include "src/objects/js-array-buffer-inl.h" |
| #include "src/objects/js-objects-inl.h" |
| #include "src/objects/maybe-object.h" |
| #include "src/objects/objects.h" |
| #include "src/objects/slots-inl.h" |
| #include "src/objects/smi.h" |
| #include "src/objects/string-forwarding-table-inl.h" |
| #include "src/objects/transitions-inl.h" |
| #include "src/objects/visitors.h" |
| #include "src/snapshot/shared-heap-serializer.h" |
| #include "src/tasks/cancelable-task.h" |
| #include "src/tracing/tracing-category-observer.h" |
| #include "src/utils/utils-inl.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| // The following has to hold in order for {MarkingState::MarkBitFrom} to not |
| // produce invalid {kImpossibleBitPattern} in the marking bitmap by overlapping. |
| static_assert(Heap::kMinObjectSizeInTaggedWords >= 2); |
| |
| // ============================================================================= |
| // Verifiers |
| // ============================================================================= |
| |
| #ifdef VERIFY_HEAP |
| namespace { |
| |
| class MarkingVerifier : public ObjectVisitorWithCageBases, public RootVisitor { |
| public: |
| virtual void Run() = 0; |
| |
| protected: |
| explicit MarkingVerifier(Heap* heap) |
| : ObjectVisitorWithCageBases(heap), heap_(heap) {} |
| |
| virtual const MarkingBitmap* bitmap(const MemoryChunk* chunk) = 0; |
| |
| virtual void VerifyMap(Map map) = 0; |
| virtual void VerifyPointers(ObjectSlot start, ObjectSlot end) = 0; |
| virtual void VerifyPointers(MaybeObjectSlot start, MaybeObjectSlot end) = 0; |
| virtual void VerifyCodePointer(CodeObjectSlot slot) = 0; |
| virtual void VerifyRootPointers(FullObjectSlot start, FullObjectSlot end) = 0; |
| |
| virtual bool IsMarked(HeapObject object) = 0; |
| |
| void VisitPointers(HeapObject host, ObjectSlot start, |
| ObjectSlot end) override { |
| VerifyPointers(start, end); |
| } |
| |
| void VisitPointers(HeapObject host, MaybeObjectSlot start, |
| MaybeObjectSlot end) override { |
| VerifyPointers(start, end); |
| } |
| |
| void VisitCodePointer(Code host, CodeObjectSlot slot) override { |
| VerifyCodePointer(slot); |
| } |
| |
| void VisitRootPointers(Root root, const char* description, |
| FullObjectSlot start, FullObjectSlot end) override { |
| VerifyRootPointers(start, end); |
| } |
| |
| void VisitMapPointer(HeapObject object) override { |
| VerifyMap(object.map(cage_base())); |
| } |
| |
| void VerifyRoots(); |
| void VerifyMarkingOnPage(const Page* page, Address start, Address end); |
| void VerifyMarking(NewSpace* new_space); |
| void VerifyMarking(PagedSpaceBase* paged_space); |
| void VerifyMarking(LargeObjectSpace* lo_space); |
| |
| Heap* heap_; |
| }; |
| |
| void MarkingVerifier::VerifyRoots() { |
| heap_->IterateRootsIncludingClients( |
| this, base::EnumSet<SkipRoot>{SkipRoot::kWeak, SkipRoot::kTopOfStack}); |
| } |
| |
| void MarkingVerifier::VerifyMarkingOnPage(const Page* page, Address start, |
| Address end) { |
| Address next_object_must_be_here_or_later = start; |
| |
| for (auto [object, size] : LiveObjectRange(page)) { |
| Address current = object.address(); |
| if (current < start) continue; |
| if (current >= end) break; |
| CHECK(IsMarked(object)); |
| CHECK(current >= next_object_must_be_here_or_later); |
| object.Iterate(cage_base(), this); |
| next_object_must_be_here_or_later = current + size; |
| // The object is either part of a black area of black allocation or a |
| // regular black object |
| CHECK(bitmap(page)->AllBitsSetInRange( |
| MarkingBitmap::AddressToIndex(current), |
| MarkingBitmap::LimitAddressToIndex( |
| next_object_must_be_here_or_later)) || |
| bitmap(page)->AllBitsClearInRange( |
| MarkingBitmap::AddressToIndex(current) + 1, |
| MarkingBitmap::LimitAddressToIndex( |
| next_object_must_be_here_or_later))); |
| current = next_object_must_be_here_or_later; |
| } |
| } |
| |
| void MarkingVerifier::VerifyMarking(NewSpace* space) { |
| if (!space) return; |
| if (v8_flags.minor_mc) { |
| VerifyMarking(PagedNewSpace::From(space)->paged_space()); |
| return; |
| } |
| Address end = space->top(); |
| // The bottom position is at the start of its page. Allows us to use |
| // page->area_start() as start of range on all pages. |
| CHECK_EQ(space->first_allocatable_address(), |
| space->first_page()->area_start()); |
| |
| PageRange range(space->first_allocatable_address(), end); |
| for (auto it = range.begin(); it != range.end();) { |
| Page* page = *(it++); |
| Address limit = it != range.end() ? page->area_end() : end; |
| CHECK(limit == end || !page->Contains(end)); |
| VerifyMarkingOnPage(page, page->area_start(), limit); |
| } |
| } |
| |
| void MarkingVerifier::VerifyMarking(PagedSpaceBase* space) { |
| for (Page* p : *space) { |
| VerifyMarkingOnPage(p, p->area_start(), p->area_end()); |
| } |
| } |
| |
| void MarkingVerifier::VerifyMarking(LargeObjectSpace* lo_space) { |
| if (!lo_space) return; |
| LargeObjectSpaceObjectIterator it(lo_space); |
| for (HeapObject obj = it.Next(); !obj.is_null(); obj = it.Next()) { |
| if (IsMarked(obj)) { |
| obj.Iterate(cage_base(), this); |
| } |
| } |
| } |
| |
| class FullMarkingVerifier : public MarkingVerifier { |
| public: |
| explicit FullMarkingVerifier(Heap* heap) |
| : MarkingVerifier(heap), |
| marking_state_(heap->non_atomic_marking_state()) {} |
| |
| void Run() override { |
| VerifyRoots(); |
| VerifyMarking(heap_->new_space()); |
| VerifyMarking(heap_->new_lo_space()); |
| VerifyMarking(heap_->old_space()); |
| VerifyMarking(heap_->code_space()); |
| if (heap_->shared_space()) VerifyMarking(heap_->shared_space()); |
| VerifyMarking(heap_->lo_space()); |
| VerifyMarking(heap_->code_lo_space()); |
| if (heap_->shared_lo_space()) VerifyMarking(heap_->shared_lo_space()); |
| } |
| |
| protected: |
| const MarkingBitmap* bitmap(const MemoryChunk* chunk) override { |
| return chunk->marking_bitmap(); |
| } |
| |
| bool IsMarked(HeapObject object) override { |
| return marking_state_->IsMarked(object); |
| } |
| |
| void VerifyMap(Map map) override { VerifyHeapObjectImpl(map); } |
| |
| void VerifyPointers(ObjectSlot start, ObjectSlot end) override { |
| VerifyPointersImpl(start, end); |
| } |
| |
| void VerifyPointers(MaybeObjectSlot start, MaybeObjectSlot end) override { |
| VerifyPointersImpl(start, end); |
| } |
| |
| void VerifyCodePointer(CodeObjectSlot slot) override { |
| Object maybe_code = slot.load(code_cage_base()); |
| HeapObject code; |
| // The slot might contain smi during Code creation, so skip it. |
| if (maybe_code.GetHeapObject(&code)) { |
| VerifyHeapObjectImpl(code); |
| } |
| } |
| |
| void VerifyRootPointers(FullObjectSlot start, FullObjectSlot end) override { |
| VerifyPointersImpl(start, end); |
| } |
| |
| void VisitCodeTarget(InstructionStream host, RelocInfo* rinfo) override { |
| InstructionStream target = |
| InstructionStream::FromTargetAddress(rinfo->target_address()); |
| VerifyHeapObjectImpl(target); |
| } |
| |
| void VisitEmbeddedPointer(InstructionStream host, RelocInfo* rinfo) override { |
| DCHECK(RelocInfo::IsEmbeddedObjectMode(rinfo->rmode())); |
| HeapObject target_object = rinfo->target_object(cage_base()); |
| Code code = Code::unchecked_cast(host.raw_code(kAcquireLoad)); |
| if (!code.IsWeakObject(target_object)) { |
| VerifyHeapObjectImpl(target_object); |
| } |
| } |
| |
| private: |
| V8_INLINE void VerifyHeapObjectImpl(HeapObject heap_object) { |
| if (!ShouldVerifyObject(heap_object)) return; |
| |
| if (heap_->MustBeInSharedOldSpace(heap_object)) { |
| CHECK(heap_->SharedHeapContains(heap_object)); |
| } |
| |
| CHECK(heap_object.InReadOnlySpace() || |
| marking_state_->IsMarked(heap_object)); |
| } |
| |
| V8_INLINE bool ShouldVerifyObject(HeapObject heap_object) { |
| const bool in_shared_heap = heap_object.InWritableSharedSpace(); |
| return heap_->isolate()->is_shared_space_isolate() ? in_shared_heap |
| : !in_shared_heap; |
| } |
| |
| template <typename TSlot> |
| V8_INLINE void VerifyPointersImpl(TSlot start, TSlot end) { |
| for (TSlot slot = start; slot < end; ++slot) { |
| typename TSlot::TObject object = slot.load(cage_base()); |
| HeapObject heap_object; |
| if (object.GetHeapObjectIfStrong(&heap_object)) { |
| VerifyHeapObjectImpl(heap_object); |
| } |
| } |
| } |
| |
| NonAtomicMarkingState* const marking_state_; |
| }; |
| |
| } // namespace |
| #endif // VERIFY_HEAP |
| |
| // ================================================================== |
| // CollectorBase, MinorMarkCompactCollector, MarkCompactCollector |
| // ================================================================== |
| |
| namespace { |
| |
| int NumberOfAvailableCores() { |
| static int num_cores = V8::GetCurrentPlatform()->NumberOfWorkerThreads() + 1; |
| // This number of cores should be greater than zero and never change. |
| DCHECK_GE(num_cores, 1); |
| DCHECK_EQ(num_cores, V8::GetCurrentPlatform()->NumberOfWorkerThreads() + 1); |
| return num_cores; |
| } |
| |
| int NumberOfParallelCompactionTasks(Heap* heap) { |
| int tasks = v8_flags.parallel_compaction ? NumberOfAvailableCores() : 1; |
| if (!heap->CanPromoteYoungAndExpandOldGeneration( |
| static_cast<size_t>(tasks * Page::kPageSize))) { |
| // Optimize for memory usage near the heap limit. |
| tasks = 1; |
| } |
| return tasks; |
| } |
| } // namespace |
| |
| CollectorBase::CollectorBase(Heap* heap, GarbageCollector collector) |
| : heap_(heap), |
| garbage_collector_(collector), |
| marking_state_(heap_->marking_state()), |
| non_atomic_marking_state_(heap_->non_atomic_marking_state()) { |
| DCHECK_NE(GarbageCollector::SCAVENGER, garbage_collector_); |
| } |
| |
| bool CollectorBase::IsMajorMC() { |
| return !heap_->IsYoungGenerationCollector(garbage_collector_); |
| } |
| |
| void CollectorBase::StartSweepSpace(PagedSpace* space) { |
| DCHECK_NE(NEW_SPACE, space->identity()); |
| space->ClearAllocatorState(); |
| |
| int will_be_swept = 0; |
| bool unused_page_present = false; |
| |
| Sweeper* sweeper = heap()->sweeper(); |
| |
| // Loop needs to support deletion if live bytes == 0 for a page. |
| for (auto it = space->begin(); it != space->end();) { |
| Page* p = *(it++); |
| DCHECK(p->SweepingDone()); |
| |
| if (p->IsEvacuationCandidate()) { |
| DCHECK_NE(NEW_SPACE, space->identity()); |
| // Will be processed in Evacuate. |
| continue; |
| } |
| |
| // One unused page is kept, all further are released before sweeping them. |
| if (non_atomic_marking_state()->live_bytes(p) == 0) { |
| if (unused_page_present) { |
| if (v8_flags.gc_verbose) { |
| PrintIsolate(isolate(), "sweeping: released page: %p", |
| static_cast<void*>(p)); |
| } |
| space->ReleasePage(p); |
| continue; |
| } |
| unused_page_present = true; |
| } |
| |
| sweeper->AddPage(space->identity(), p, Sweeper::REGULAR); |
| will_be_swept++; |
| } |
| |
| if (v8_flags.gc_verbose) { |
| PrintIsolate(isolate(), "sweeping: space=%s initialized_for_sweeping=%d", |
| space->name(), will_be_swept); |
| } |
| } |
| |
| bool CollectorBase::IsCppHeapMarkingFinished() const { |
| const auto* cpp_heap = CppHeap::From(heap_->cpp_heap()); |
| if (!cpp_heap) return true; |
| |
| return cpp_heap->IsTracingDone() && |
| local_marking_worklists()->IsWrapperEmpty(); |
| } |
| |
| MarkCompactCollector::MarkCompactCollector(Heap* heap) |
| : CollectorBase(heap, GarbageCollector::MARK_COMPACTOR), |
| #ifdef DEBUG |
| state_(IDLE), |
| #endif |
| uses_shared_heap_(isolate()->has_shared_space()), |
| is_shared_space_isolate_(isolate()->is_shared_space_isolate()), |
| sweeper_(heap_->sweeper()) { |
| } |
| |
| MarkCompactCollector::~MarkCompactCollector() = default; |
| |
| void MarkCompactCollector::SetUp() {} |
| |
| void MarkCompactCollector::TearDown() { |
| AbortCompaction(); |
| if (heap()->incremental_marking()->IsMajorMarking()) { |
| local_marking_worklists()->Publish(); |
| heap()->main_thread_local_heap()->marking_barrier()->PublishIfNeeded(); |
| // Marking barriers of LocalHeaps will be published in their destructors. |
| marking_worklists()->Clear(); |
| local_weak_objects()->Publish(); |
| weak_objects()->Clear(); |
| } |
| } |
| |
| void MarkCompactCollector::AddEvacuationCandidate(Page* p) { |
| DCHECK(!p->NeverEvacuate()); |
| |
| if (v8_flags.trace_evacuation_candidates) { |
| PrintIsolate( |
| isolate(), |
| "Evacuation candidate: Free bytes: %6zu. Free Lists length: %4d.\n", |
| p->area_size() - p->allocated_bytes(), p->FreeListsLength()); |
| } |
| |
| p->MarkEvacuationCandidate(); |
| evacuation_candidates_.push_back(p); |
| } |
| |
| static void TraceFragmentation(PagedSpace* space) { |
| int number_of_pages = space->CountTotalPages(); |
| intptr_t reserved = (number_of_pages * space->AreaSize()); |
| intptr_t free = reserved - space->SizeOfObjects(); |
| PrintF("[%s]: %d pages, %d (%.1f%%) free\n", space->name(), number_of_pages, |
| static_cast<int>(free), static_cast<double>(free) * 100 / reserved); |
| } |
| |
| bool MarkCompactCollector::StartCompaction(StartCompactionMode mode) { |
| DCHECK(!compacting_); |
| DCHECK(evacuation_candidates_.empty()); |
| |
| // Bailouts for completely disabled compaction. |
| if (!v8_flags.compact || |
| (mode == StartCompactionMode::kAtomic && heap()->IsGCWithStack() && |
| !v8_flags.compact_with_stack) || |
| (v8_flags.gc_experiment_less_compaction && |
| !heap_->ShouldReduceMemory())) { |
| return false; |
| } |
| |
| CollectEvacuationCandidates(heap()->old_space()); |
| |
| if (heap()->shared_space()) { |
| CollectEvacuationCandidates(heap()->shared_space()); |
| } |
| |
| if (v8_flags.compact_code_space && |
| (!heap()->IsGCWithStack() || v8_flags.compact_code_space_with_stack)) { |
| CollectEvacuationCandidates(heap()->code_space()); |
| } else if (v8_flags.trace_fragmentation) { |
| TraceFragmentation(heap()->code_space()); |
| } |
| |
| compacting_ = !evacuation_candidates_.empty(); |
| return compacting_; |
| } |
| |
| namespace { |
| void VisitObjectWithEmbedderFields(JSObject object, |
| MarkingWorklists::Local& worklist) { |
| DCHECK(object.MayHaveEmbedderFields()); |
| DCHECK(!Heap::InYoungGeneration(object)); |
| |
| MarkingWorklists::Local::WrapperSnapshot wrapper_snapshot; |
| const bool valid_snapshot = |
| worklist.ExtractWrapper(object.map(), object, wrapper_snapshot); |
| DCHECK(valid_snapshot); |
| USE(valid_snapshot); |
| worklist.PushExtractedWrapper(wrapper_snapshot); |
| } |
| } // namespace |
| |
| void MarkCompactCollector::StartMarking() { |
| std::vector<Address> contexts = |
| heap()->memory_measurement()->StartProcessing(); |
| if (v8_flags.stress_per_context_marking_worklist) { |
| contexts.clear(); |
| HandleScope handle_scope(heap()->isolate()); |
| for (auto context : heap()->FindAllNativeContexts()) { |
| contexts.push_back(context->ptr()); |
| } |
| } |
| code_flush_mode_ = Heap::GetCodeFlushMode(isolate()); |
| marking_worklists()->CreateContextWorklists(contexts); |
| auto* cpp_heap = CppHeap::From(heap_->cpp_heap()); |
| local_marking_worklists_ = std::make_unique<MarkingWorklists::Local>( |
| marking_worklists(), |
| cpp_heap ? cpp_heap->CreateCppMarkingStateForMutatorThread() |
| : MarkingWorklists::Local::kNoCppMarkingState); |
| local_weak_objects_ = std::make_unique<WeakObjects::Local>(weak_objects()); |
| marking_visitor_ = std::make_unique<MarkingVisitor>( |
| marking_state(), local_marking_worklists(), local_weak_objects_.get(), |
| heap_, epoch(), code_flush_mode(), heap_->cpp_heap(), |
| heap_->ShouldCurrentGCKeepAgesUnchanged()); |
| // Marking bits are cleared by the sweeper. |
| #ifdef VERIFY_HEAP |
| if (v8_flags.verify_heap) { |
| VerifyMarkbitsAreClean(); |
| } |
| #endif // VERIFY_HEAP |
| } |
| |
| void MarkCompactCollector::CollectGarbage() { |
| // Make sure that Prepare() has been called. The individual steps below will |
| // update the state as they proceed. |
| DCHECK(state_ == PREPARE_GC); |
| |
| MarkLiveObjects(); |
| ClearNonLiveReferences(); |
| VerifyMarking(); |
| heap()->memory_measurement()->FinishProcessing(native_context_stats_); |
| RecordObjectStats(); |
| |
| Sweep(); |
| Evacuate(); |
| Finish(); |
| } |
| |
| #ifdef VERIFY_HEAP |
| void MarkCompactCollector::VerifyMarkbitsAreClean(PagedSpaceBase* space) { |
| for (Page* p : *space) { |
| CHECK(non_atomic_marking_state()->bitmap(p)->IsClean()); |
| CHECK_EQ(0, non_atomic_marking_state()->live_bytes(p)); |
| } |
| } |
| |
| void MarkCompactCollector::VerifyMarkbitsAreClean(NewSpace* space) { |
| if (!space) return; |
| if (v8_flags.minor_mc) { |
| VerifyMarkbitsAreClean(PagedNewSpace::From(space)->paged_space()); |
| return; |
| } |
| for (Page* p : PageRange(space->first_allocatable_address(), space->top())) { |
| CHECK(non_atomic_marking_state()->bitmap(p)->IsClean()); |
| CHECK_EQ(0, non_atomic_marking_state()->live_bytes(p)); |
| } |
| } |
| |
| void MarkCompactCollector::VerifyMarkbitsAreClean(LargeObjectSpace* space) { |
| if (!space) return; |
| LargeObjectSpaceObjectIterator it(space); |
| for (HeapObject obj = it.Next(); !obj.is_null(); obj = it.Next()) { |
| CHECK(non_atomic_marking_state()->IsUnmarked(obj)); |
| CHECK_EQ(0, non_atomic_marking_state()->live_bytes( |
| MemoryChunk::FromHeapObject(obj))); |
| } |
| } |
| |
| void MarkCompactCollector::VerifyMarkbitsAreClean() { |
| VerifyMarkbitsAreClean(heap_->old_space()); |
| VerifyMarkbitsAreClean(heap_->code_space()); |
| VerifyMarkbitsAreClean(heap_->new_space()); |
| VerifyMarkbitsAreClean(heap_->lo_space()); |
| VerifyMarkbitsAreClean(heap_->code_lo_space()); |
| VerifyMarkbitsAreClean(heap_->new_lo_space()); |
| } |
| |
| #endif // VERIFY_HEAP |
| |
| void MarkCompactCollector::ComputeEvacuationHeuristics( |
| size_t area_size, int* target_fragmentation_percent, |
| size_t* max_evacuated_bytes) { |
| // For memory reducing and optimize for memory mode we directly define both |
| // constants. |
| const int kTargetFragmentationPercentForReduceMemory = 20; |
| const size_t kMaxEvacuatedBytesForReduceMemory = 12 * MB; |
| const int kTargetFragmentationPercentForOptimizeMemory = 20; |
| const size_t kMaxEvacuatedBytesForOptimizeMemory = 6 * MB; |
| |
| // For regular mode (which is latency critical) we define less aggressive |
| // defaults to start and switch to a trace-based (using compaction speed) |
| // approach as soon as we have enough samples. |
| const int kTargetFragmentationPercent = 70; |
| const size_t kMaxEvacuatedBytes = 4 * MB; |
| // Time to take for a single area (=payload of page). Used as soon as there |
| // exist enough compaction speed samples. |
| const float kTargetMsPerArea = .5; |
| |
| if (heap()->ShouldReduceMemory()) { |
| *target_fragmentation_percent = kTargetFragmentationPercentForReduceMemory; |
| *max_evacuated_bytes = kMaxEvacuatedBytesForReduceMemory; |
| } else if (heap()->ShouldOptimizeForMemoryUsage()) { |
| *target_fragmentation_percent = |
| kTargetFragmentationPercentForOptimizeMemory; |
| *max_evacuated_bytes = kMaxEvacuatedBytesForOptimizeMemory; |
| } else { |
| const double estimated_compaction_speed = |
| heap()->tracer()->CompactionSpeedInBytesPerMillisecond(); |
| if (estimated_compaction_speed != 0) { |
| // Estimate the target fragmentation based on traced compaction speed |
| // and a goal for a single page. |
| const double estimated_ms_per_area = |
| 1 + area_size / estimated_compaction_speed; |
| *target_fragmentation_percent = static_cast<int>( |
| 100 - 100 * kTargetMsPerArea / estimated_ms_per_area); |
| if (*target_fragmentation_percent < |
| kTargetFragmentationPercentForReduceMemory) { |
| *target_fragmentation_percent = |
| kTargetFragmentationPercentForReduceMemory; |
| } |
| } else { |
| *target_fragmentation_percent = kTargetFragmentationPercent; |
| } |
| *max_evacuated_bytes = kMaxEvacuatedBytes; |
| } |
| } |
| |
| void MarkCompactCollector::CollectEvacuationCandidates(PagedSpace* space) { |
| DCHECK(space->identity() == OLD_SPACE || space->identity() == CODE_SPACE || |
| space->identity() == SHARED_SPACE); |
| |
| int number_of_pages = space->CountTotalPages(); |
| size_t area_size = space->AreaSize(); |
| |
| const bool in_standard_path = |
| !(v8_flags.manual_evacuation_candidates_selection || |
| v8_flags.stress_compaction_random || v8_flags.stress_compaction || |
| v8_flags.compact_on_every_full_gc); |
| // Those variables will only be initialized if |in_standard_path|, and are not |
| // used otherwise. |
| size_t max_evacuated_bytes; |
| int target_fragmentation_percent; |
| size_t free_bytes_threshold; |
| if (in_standard_path) { |
| // We use two conditions to decide whether a page qualifies as an evacuation |
| // candidate, or not: |
| // * Target fragmentation: How fragmented is a page, i.e., how is the ratio |
| // between live bytes and capacity of this page (= area). |
| // * Evacuation quota: A global quota determining how much bytes should be |
| // compacted. |
| ComputeEvacuationHeuristics(area_size, &target_fragmentation_percent, |
| &max_evacuated_bytes); |
| free_bytes_threshold = target_fragmentation_percent * (area_size / 100); |
| } |
| |
| // Pairs of (live_bytes_in_page, page). |
| using LiveBytesPagePair = std::pair<size_t, Page*>; |
| std::vector<LiveBytesPagePair> pages; |
| pages.reserve(number_of_pages); |
| |
| CodePageHeaderModificationScope rwx_write_scope( |
| "Modification of Code page header flags requires write " |
| "access"); |
| |
| DCHECK(!sweeper()->sweeping_in_progress()); |
| Page* owner_of_linear_allocation_area = |
| space->top() == space->limit() |
| ? nullptr |
| : Page::FromAllocationAreaAddress(space->top()); |
| for (Page* p : *space) { |
| if (p->NeverEvacuate() || (p == owner_of_linear_allocation_area) || |
| !p->CanAllocate()) |
| continue; |
| |
| if (p->IsPinned()) { |
| DCHECK( |
| !p->IsFlagSet(MemoryChunk::FORCE_EVACUATION_CANDIDATE_FOR_TESTING)); |
| continue; |
| } |
| |
| // Invariant: Evacuation candidates are just created when marking is |
| // started. This means that sweeping has finished. Furthermore, at the end |
| // of a GC all evacuation candidates are cleared and their slot buffers are |
| // released. |
| CHECK(!p->IsEvacuationCandidate()); |
| CHECK_NULL(p->slot_set<OLD_TO_OLD>()); |
| CHECK_NULL(p->typed_slot_set<OLD_TO_OLD>()); |
| CHECK(p->SweepingDone()); |
| DCHECK(p->area_size() == area_size); |
| if (in_standard_path) { |
| // Only the pages with at more than |free_bytes_threshold| free bytes are |
| // considered for evacuation. |
| if (area_size - p->allocated_bytes() >= free_bytes_threshold) { |
| pages.push_back(std::make_pair(p->allocated_bytes(), p)); |
| } |
| } else { |
| pages.push_back(std::make_pair(p->allocated_bytes(), p)); |
| } |
| } |
| |
| int candidate_count = 0; |
| size_t total_live_bytes = 0; |
| |
| const bool reduce_memory = heap()->ShouldReduceMemory(); |
| if (v8_flags.manual_evacuation_candidates_selection) { |
| for (size_t i = 0; i < pages.size(); i++) { |
| Page* p = pages[i].second; |
| if (p->IsFlagSet(MemoryChunk::FORCE_EVACUATION_CANDIDATE_FOR_TESTING)) { |
| candidate_count++; |
| total_live_bytes += pages[i].first; |
| p->ClearFlag(MemoryChunk::FORCE_EVACUATION_CANDIDATE_FOR_TESTING); |
| AddEvacuationCandidate(p); |
| } |
| } |
| } else if (v8_flags.stress_compaction_random) { |
| double fraction = isolate()->fuzzer_rng()->NextDouble(); |
| size_t pages_to_mark_count = |
| static_cast<size_t>(fraction * (pages.size() + 1)); |
| for (uint64_t i : isolate()->fuzzer_rng()->NextSample( |
| pages.size(), pages_to_mark_count)) { |
| candidate_count++; |
| total_live_bytes += pages[i].first; |
| AddEvacuationCandidate(pages[i].second); |
| } |
| } else if (v8_flags.stress_compaction) { |
| for (size_t i = 0; i < pages.size(); i++) { |
| Page* p = pages[i].second; |
| if (i % 2 == 0) { |
| candidate_count++; |
| total_live_bytes += pages[i].first; |
| AddEvacuationCandidate(p); |
| } |
| } |
| } else { |
| // The following approach determines the pages that should be evacuated. |
| // |
| // Sort pages from the most free to the least free, then select |
| // the first n pages for evacuation such that: |
| // - the total size of evacuated objects does not exceed the specified |
| // limit. |
| // - fragmentation of (n+1)-th page does not exceed the specified limit. |
| std::sort(pages.begin(), pages.end(), |
| [](const LiveBytesPagePair& a, const LiveBytesPagePair& b) { |
| return a.first < b.first; |
| }); |
| for (size_t i = 0; i < pages.size(); i++) { |
| size_t live_bytes = pages[i].first; |
| DCHECK_GE(area_size, live_bytes); |
| if (v8_flags.compact_on_every_full_gc || |
| ((total_live_bytes + live_bytes) <= max_evacuated_bytes)) { |
| candidate_count++; |
| total_live_bytes += live_bytes; |
| } |
| if (v8_flags.trace_fragmentation_verbose) { |
| PrintIsolate(isolate(), |
| "compaction-selection-page: space=%s free_bytes_page=%zu " |
| "fragmentation_limit_kb=%zu " |
| "fragmentation_limit_percent=%d sum_compaction_kb=%zu " |
| "compaction_limit_kb=%zu\n", |
| space->name(), (area_size - live_bytes) / KB, |
| free_bytes_threshold / KB, target_fragmentation_percent, |
| total_live_bytes / KB, max_evacuated_bytes / KB); |
| } |
| } |
| // How many pages we will allocated for the evacuated objects |
| // in the worst case: ceil(total_live_bytes / area_size) |
| int estimated_new_pages = |
| static_cast<int>((total_live_bytes + area_size - 1) / area_size); |
| DCHECK_LE(estimated_new_pages, candidate_count); |
| int estimated_released_pages = candidate_count - estimated_new_pages; |
| // Avoid (compact -> expand) cycles. |
| if ((estimated_released_pages == 0) && !v8_flags.compact_on_every_full_gc) { |
| candidate_count = 0; |
| } |
| for (int i = 0; i < candidate_count; i++) { |
| AddEvacuationCandidate(pages[i].second); |
| } |
| } |
| |
| if (v8_flags.trace_fragmentation) { |
| PrintIsolate(isolate(), |
| "compaction-selection: space=%s reduce_memory=%d pages=%d " |
| "total_live_bytes=%zu\n", |
| space->name(), reduce_memory, candidate_count, |
| total_live_bytes / KB); |
| } |
| } |
| |
| void MarkCompactCollector::AbortCompaction() { |
| if (compacting_) { |
| CodePageHeaderModificationScope rwx_write_scope( |
| "Changing Code page flags and remembered sets require " |
| "write access " |
| "to the page header"); |
| RememberedSet<OLD_TO_OLD>::ClearAll(heap()); |
| RememberedSet<OLD_TO_CODE>::ClearAll(heap()); |
| for (Page* p : evacuation_candidates_) { |
| p->ClearEvacuationCandidate(); |
| } |
| compacting_ = false; |
| evacuation_candidates_.clear(); |
| } |
| DCHECK(evacuation_candidates_.empty()); |
| } |
| |
| void MarkCompactCollector::Prepare() { |
| #ifdef DEBUG |
| DCHECK(state_ == IDLE); |
| state_ = PREPARE_GC; |
| #endif |
| |
| DCHECK(!sweeper()->sweeping_in_progress()); |
| |
| // Unmapper tasks needs to be stopped during the GC, otherwise pages queued |
| // for freeing might get unmapped during the GC. |
| DCHECK(!heap_->memory_allocator()->unmapper()->IsRunning()); |
| |
| if (!heap()->incremental_marking()->IsMarking()) { |
| if (heap()->cpp_heap()) { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_MARK_EMBEDDER_PROLOGUE); |
| // InitializeTracing should be called before visitor initialization in |
| // StartMarking. |
| CppHeap::From(heap()->cpp_heap()) |
| ->InitializeTracing(CppHeap::CollectionType::kMajor); |
| } |
| StartCompaction(StartCompactionMode::kAtomic); |
| StartMarking(); |
| if (heap()->cpp_heap()) { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_MARK_EMBEDDER_PROLOGUE); |
| // StartTracing immediately starts marking which requires V8 worklists to |
| // be set up. |
| CppHeap::From(heap()->cpp_heap())->StartTracing(); |
| } |
| #ifdef V8_COMPRESS_POINTERS |
| heap_->isolate()->external_pointer_table().StartCompactingIfNeeded(); |
| #endif // V8_COMPRESS_POINTERS |
| } |
| |
| heap_->FreeLinearAllocationAreas(); |
| |
| NewSpace* new_space = heap()->new_space(); |
| if (new_space) { |
| DCHECK_EQ(new_space->top(), new_space->original_top_acquire()); |
| } |
| } |
| |
| void MarkCompactCollector::FinishConcurrentMarking() { |
| // FinishConcurrentMarking is called for both, concurrent and parallel, |
| // marking. It is safe to call this function when tasks are already finished. |
| DCHECK_EQ(heap()->concurrent_marking()->garbage_collector(), |
| GarbageCollector::MARK_COMPACTOR); |
| if (v8_flags.parallel_marking || v8_flags.concurrent_marking) { |
| heap()->concurrent_marking()->Join(); |
| heap()->concurrent_marking()->FlushMemoryChunkData( |
| non_atomic_marking_state()); |
| heap()->concurrent_marking()->FlushNativeContexts(&native_context_stats_); |
| } |
| if (auto* cpp_heap = CppHeap::From(heap_->cpp_heap())) { |
| cpp_heap->FinishConcurrentMarkingIfNeeded(); |
| } |
| } |
| |
| void MarkCompactCollector::VerifyMarking() { |
| CHECK(local_marking_worklists()->IsEmpty()); |
| DCHECK(heap_->incremental_marking()->IsStopped()); |
| #ifdef VERIFY_HEAP |
| if (v8_flags.verify_heap) { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_MARK_VERIFY); |
| FullMarkingVerifier verifier(heap()); |
| verifier.Run(); |
| heap()->old_space()->VerifyLiveBytes(); |
| heap()->code_space()->VerifyLiveBytes(); |
| if (heap()->shared_space()) heap()->shared_space()->VerifyLiveBytes(); |
| if (v8_flags.minor_mc && heap()->paged_new_space()) |
| heap()->paged_new_space()->paged_space()->VerifyLiveBytes(); |
| } |
| #endif // VERIFY_HEAP |
| } |
| |
| namespace { |
| |
| void ShrinkPagesToObjectSizes(Heap* heap, OldLargeObjectSpace* space) { |
| size_t surviving_object_size = 0; |
| PtrComprCageBase cage_base(heap->isolate()); |
| for (auto it = space->begin(); it != space->end();) { |
| LargePage* current = *(it++); |
| HeapObject object = current->GetObject(); |
| const size_t object_size = static_cast<size_t>(object.Size(cage_base)); |
| space->ShrinkPageToObjectSize(current, object, object_size); |
| surviving_object_size += object_size; |
| } |
| space->set_objects_size(surviving_object_size); |
| } |
| |
| } // namespace |
| |
| void MarkCompactCollector::Finish() { |
| { |
| TRACE_GC_EPOCH(heap()->tracer(), GCTracer::Scope::MC_SWEEP, |
| ThreadKind::kMain); |
| |
| DCHECK_IMPLIES(!v8_flags.minor_mc, |
| empty_new_space_pages_to_be_swept_.empty()); |
| if (!empty_new_space_pages_to_be_swept_.empty()) { |
| GCTracer::Scope sweep_scope( |
| heap()->tracer(), GCTracer::Scope::MC_SWEEP_NEW, ThreadKind::kMain); |
| for (Page* p : empty_new_space_pages_to_be_swept_) { |
| sweeper()->SweepEmptyNewSpacePage(p); |
| } |
| empty_new_space_pages_to_be_swept_.clear(); |
| } |
| |
| if (heap()->new_lo_space()) { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_SWEEP_NEW_LO); |
| SweepLargeSpace(heap()->new_lo_space()); |
| } |
| |
| #ifdef DEBUG |
| heap()->VerifyCountersBeforeConcurrentSweeping(garbage_collector_); |
| #endif // DEBUG |
| } |
| |
| if (heap()->new_space()) { |
| if (v8_flags.minor_mc) { |
| switch (resize_new_space_) { |
| case ResizeNewSpaceMode::kShrink: |
| heap()->ReduceNewSpaceSize(); |
| break; |
| case ResizeNewSpaceMode::kGrow: |
| heap()->ExpandNewSpaceSize(); |
| break; |
| case ResizeNewSpaceMode::kNone: |
| break; |
| } |
| resize_new_space_ = ResizeNewSpaceMode::kNone; |
| } |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_EVACUATE); |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_EVACUATE_REBALANCE); |
| if (!heap()->new_space()->EnsureCurrentCapacity()) { |
| heap()->FatalProcessOutOfMemory("NewSpace::EnsureCurrentCapacity"); |
| } |
| } |
| |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_FINISH); |
| |
| if (heap()->new_space()) heap()->new_space()->GarbageCollectionEpilogue(); |
| |
| auto* isolate = heap()->isolate(); |
| isolate->global_handles()->ClearListOfYoungNodes(); |
| isolate->traced_handles()->ClearListOfYoungNodes(); |
| |
| SweepArrayBufferExtensions(); |
| |
| marking_visitor_.reset(); |
| local_marking_worklists_.reset(); |
| marking_worklists_.ReleaseContextWorklists(); |
| native_context_stats_.Clear(); |
| |
| CHECK(weak_objects_.current_ephemerons.IsEmpty()); |
| CHECK(weak_objects_.discovered_ephemerons.IsEmpty()); |
| local_weak_objects_->next_ephemerons_local.Publish(); |
| local_weak_objects_.reset(); |
| weak_objects_.next_ephemerons.Clear(); |
| |
| sweeper()->StartSweeperTasks(); |
| |
| // Ensure unmapper tasks are stopped such that queued pages aren't freed |
| // before this point. We still need all pages to be accessible for the "update |
| // pointers" phase. |
| DCHECK(!heap_->memory_allocator()->unmapper()->IsRunning()); |
| |
| // Shrink pages if possible after processing and filtering slots. |
| ShrinkPagesToObjectSizes(heap(), heap()->lo_space()); |
| |
| #ifdef DEBUG |
| DCHECK(state_ == SWEEP_SPACES || state_ == RELOCATE_OBJECTS); |
| state_ = IDLE; |
| #endif |
| |
| if (have_code_to_deoptimize_) { |
| // Some code objects were marked for deoptimization during the GC. |
| Deoptimizer::DeoptimizeMarkedCode(isolate); |
| have_code_to_deoptimize_ = false; |
| } |
| } |
| |
| void MarkCompactCollector::SweepArrayBufferExtensions() { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_FINISH_SWEEP_ARRAY_BUFFERS); |
| DCHECK_IMPLIES(heap_->new_space(), heap_->new_space()->Size() == 0); |
| DCHECK_IMPLIES(heap_->new_lo_space(), heap_->new_lo_space()->Size() == 0); |
| heap_->array_buffer_sweeper()->RequestSweep( |
| ArrayBufferSweeper::SweepingType::kFull, |
| ArrayBufferSweeper::TreatAllYoungAsPromoted::kYes); |
| } |
| |
| class MarkCompactCollector::RootMarkingVisitor final : public RootVisitor { |
| public: |
| explicit RootMarkingVisitor(MarkCompactCollector* collector) |
| : collector_(collector) {} |
| |
| void VisitRootPointer(Root root, const char* description, |
| FullObjectSlot p) final { |
| DCHECK(!MapWord::IsPacked(p.Relaxed_Load().ptr())); |
| MarkObjectByPointer(root, p); |
| } |
| |
| void VisitRootPointers(Root root, const char* description, |
| FullObjectSlot start, FullObjectSlot end) final { |
| for (FullObjectSlot p = start; p < end; ++p) { |
| MarkObjectByPointer(root, p); |
| } |
| } |
| |
| // Keep this synced with RootsReferencesExtractor::VisitRunningCode. |
| void VisitRunningCode(FullObjectSlot code_slot, |
| FullObjectSlot istream_or_smi_zero_slot) final { |
| Object istream_or_smi_zero = *istream_or_smi_zero_slot; |
| DCHECK(istream_or_smi_zero == Smi::zero() || |
| istream_or_smi_zero.IsInstructionStream()); |
| Code code = Code::cast(*code_slot); |
| DCHECK_EQ(code.raw_instruction_stream( |
| PtrComprCageBase{collector_->isolate()->code_cage_base()}), |
| istream_or_smi_zero); |
| |
| // We must not remove deoptimization literals which may be needed in |
| // order to successfully deoptimize. |
| code.IterateDeoptimizationLiterals(this); |
| |
| if (istream_or_smi_zero != Smi::zero()) { |
| VisitRootPointer(Root::kStackRoots, nullptr, istream_or_smi_zero_slot); |
| } |
| |
| VisitRootPointer(Root::kStackRoots, nullptr, code_slot); |
| } |
| |
| private: |
| V8_INLINE void MarkObjectByPointer(Root root, FullObjectSlot p) { |
| Object object = *p; |
| if (!object.IsHeapObject()) return; |
| HeapObject heap_object = HeapObject::cast(object); |
| if (!collector_->ShouldMarkObject(heap_object)) return; |
| collector_->MarkRootObject(root, heap_object); |
| } |
| |
| MarkCompactCollector* const collector_; |
| }; |
| |
| class MarkCompactCollector::ClientRootMarkingVisitor final |
| : public RootVisitor { |
| public: |
| explicit ClientRootMarkingVisitor(MarkCompactCollector* collector) |
| : collector_(collector) {} |
| |
| void VisitRootPointer(Root root, const char* description, |
| FullObjectSlot p) final { |
| DCHECK(!MapWord::IsPacked(p.Relaxed_Load().ptr())); |
| MarkObjectByPointer(root, p); |
| } |
| |
| void VisitRootPointers(Root root, const char* description, |
| FullObjectSlot start, FullObjectSlot end) final { |
| for (FullObjectSlot p = start; p < end; ++p) { |
| MarkObjectByPointer(root, p); |
| } |
| } |
| |
| void VisitRunningCode(FullObjectSlot code_slot, |
| FullObjectSlot istream_or_smi_zero_slot) final { |
| #if DEBUG |
| DCHECK(!HeapObject::cast(*code_slot).InWritableSharedSpace()); |
| Object maybe_istream = *istream_or_smi_zero_slot; |
| DCHECK(maybe_istream == Smi::zero() || |
| !HeapObject::cast(maybe_istream).InWritableSharedSpace()); |
| #endif |
| } |
| |
| private: |
| V8_INLINE void MarkObjectByPointer(Root root, FullObjectSlot p) { |
| Object object = *p; |
| if (!object.IsHeapObject()) return; |
| HeapObject heap_object = HeapObject::cast(object); |
| // In clients we only care about pointers into the shared spaces. |
| if (!heap_object.InWritableSharedSpace()) return; |
| collector_->MarkRootObject(root, heap_object); |
| } |
| |
| MarkCompactCollector* const collector_; |
| }; |
| |
| // This visitor is used to visit the body of special objects held alive by |
| // other roots. |
| // |
| // It is currently used for |
| // - InstructionStream held alive by the top optimized frame. This code cannot |
| // be deoptimized and thus have to be kept alive in an isolate way, i.e., it |
| // should not keep alive other code objects reachable through the weak list but |
| // they should keep alive its embedded pointers (which would otherwise be |
| // dropped). |
| // - Prefix of the string table. |
| // - If V8_ENABLE_SANDBOX, client Isolates' waiter queue node |
| // ExternalPointer_t in shared Isolates. |
| class MarkCompactCollector::CustomRootBodyMarkingVisitor final |
| : public ObjectVisitorWithCageBases { |
| public: |
| explicit CustomRootBodyMarkingVisitor(MarkCompactCollector* collector) |
| : ObjectVisitorWithCageBases(collector->isolate()), |
| collector_(collector) {} |
| |
| void VisitPointer(HeapObject host, ObjectSlot p) final { |
| MarkObject(host, p.load(cage_base())); |
| } |
| |
| void VisitMapPointer(HeapObject host) final { |
| MarkObject(host, host.map(cage_base())); |
| } |
| |
| void VisitPointers(HeapObject host, ObjectSlot start, ObjectSlot end) final { |
| for (ObjectSlot p = start; p < end; ++p) { |
| // The map slot should be handled in VisitMapPointer. |
| DCHECK_NE(host.map_slot(), p); |
| DCHECK(!HasWeakHeapObjectTag(p.load(cage_base()))); |
| MarkObject(host, p.load(cage_base())); |
| } |
| } |
| |
| void VisitCodePointer(Code host, CodeObjectSlot slot) override { |
| MarkObject(host, slot.load(code_cage_base())); |
| } |
| |
| void VisitPointers(HeapObject host, MaybeObjectSlot start, |
| MaybeObjectSlot end) final { |
| // At the moment, custom roots cannot contain weak pointers. |
| UNREACHABLE(); |
| } |
| |
| void VisitCodeTarget(InstructionStream host, RelocInfo* rinfo) override { |
| InstructionStream target = |
| InstructionStream::FromTargetAddress(rinfo->target_address()); |
| MarkObject(host, target); |
| } |
| |
| void VisitEmbeddedPointer(InstructionStream host, RelocInfo* rinfo) override { |
| MarkObject(host, rinfo->target_object(cage_base())); |
| } |
| |
| private: |
| V8_INLINE void MarkObject(HeapObject host, Object object) { |
| if (!object.IsHeapObject()) return; |
| HeapObject heap_object = HeapObject::cast(object); |
| if (!collector_->ShouldMarkObject(heap_object)) return; |
| collector_->MarkObject(host, heap_object); |
| } |
| |
| MarkCompactCollector* const collector_; |
| }; |
| |
| class MarkCompactCollector::ClientCustomRootBodyMarkingVisitor final |
| : public ObjectVisitorWithCageBases { |
| public: |
| explicit ClientCustomRootBodyMarkingVisitor(MarkCompactCollector* collector) |
| : ObjectVisitorWithCageBases(collector->isolate()), |
| collector_(collector) {} |
| |
| void VisitPointer(HeapObject host, ObjectSlot p) final { |
| MarkObject(host, p.load(cage_base())); |
| } |
| |
| void VisitMapPointer(HeapObject host) final { |
| MarkObject(host, host.map(cage_base())); |
| } |
| |
| void VisitPointers(HeapObject host, ObjectSlot start, ObjectSlot end) final { |
| for (ObjectSlot p = start; p < end; ++p) { |
| // The map slot should be handled in VisitMapPointer. |
| DCHECK_NE(host.map_slot(), p); |
| DCHECK(!HasWeakHeapObjectTag(p.load(cage_base()))); |
| MarkObject(host, p.load(cage_base())); |
| } |
| } |
| |
| void VisitCodePointer(Code host, CodeObjectSlot slot) override { |
| #if DEBUG |
| Object istream_object = slot.load(code_cage_base()); |
| InstructionStream istream; |
| if (istream_object.GetHeapObject(&istream)) { |
| DCHECK(!istream.InWritableSharedSpace()); |
| } |
| #endif |
| } |
| |
| void VisitPointers(HeapObject host, MaybeObjectSlot start, |
| MaybeObjectSlot end) final { |
| // At the moment, custom roots cannot contain weak pointers. |
| UNREACHABLE(); |
| } |
| |
| void VisitCodeTarget(InstructionStream host, RelocInfo* rinfo) override { |
| #if DEBUG |
| InstructionStream target = |
| InstructionStream::FromTargetAddress(rinfo->target_address()); |
| DCHECK(!target.InWritableSharedSpace()); |
| #endif |
| } |
| |
| void VisitEmbeddedPointer(InstructionStream host, RelocInfo* rinfo) override { |
| MarkObject(host, rinfo->target_object(cage_base())); |
| } |
| |
| private: |
| V8_INLINE void MarkObject(HeapObject host, Object object) { |
| if (!object.IsHeapObject()) return; |
| HeapObject heap_object = HeapObject::cast(object); |
| if (!heap_object.InWritableSharedSpace()) return; |
| collector_->MarkObject(host, heap_object); |
| } |
| |
| MarkCompactCollector* const collector_; |
| }; |
| |
| class MarkCompactCollector::SharedHeapObjectVisitor final |
| : public ObjectVisitorWithCageBases { |
| public: |
| explicit SharedHeapObjectVisitor(MarkCompactCollector* collector) |
| : ObjectVisitorWithCageBases(collector->isolate()), |
| collector_(collector) {} |
| |
| void VisitPointer(HeapObject host, ObjectSlot p) final { |
| CheckForSharedObject(host, p, p.load(cage_base())); |
| } |
| |
| void VisitPointer(HeapObject host, MaybeObjectSlot p) final { |
| MaybeObject object = p.load(cage_base()); |
| HeapObject heap_object; |
| if (object.GetHeapObject(&heap_object)) |
| CheckForSharedObject(host, ObjectSlot(p), heap_object); |
| } |
| |
| void VisitMapPointer(HeapObject host) final { |
| CheckForSharedObject(host, host.map_slot(), host.map(cage_base())); |
| } |
| |
| void VisitPointers(HeapObject host, ObjectSlot start, ObjectSlot end) final { |
| for (ObjectSlot p = start; p < end; ++p) { |
| // The map slot should be handled in VisitMapPointer. |
| DCHECK_NE(host.map_slot(), p); |
| DCHECK(!HasWeakHeapObjectTag(p.load(cage_base()))); |
| CheckForSharedObject(host, p, p.load(cage_base())); |
| } |
| } |
| |
| void VisitCodePointer(Code host, CodeObjectSlot slot) override { |
| UNREACHABLE(); |
| } |
| |
| void VisitPointers(HeapObject host, MaybeObjectSlot start, |
| MaybeObjectSlot end) final { |
| for (MaybeObjectSlot p = start; p < end; ++p) { |
| // The map slot should be handled in VisitMapPointer. |
| DCHECK_NE(host.map_slot(), ObjectSlot(p)); |
| VisitPointer(host, p); |
| } |
| } |
| |
| void VisitCodeTarget(InstructionStream host, RelocInfo* rinfo) override { |
| UNREACHABLE(); |
| } |
| |
| void VisitEmbeddedPointer(InstructionStream host, RelocInfo* rinfo) override { |
| UNREACHABLE(); |
| } |
| |
| private: |
| V8_INLINE void CheckForSharedObject(HeapObject host, ObjectSlot slot, |
| Object object) { |
| DCHECK(!host.InAnySharedSpace()); |
| if (!object.IsHeapObject()) return; |
| HeapObject heap_object = HeapObject::cast(object); |
| if (!heap_object.InWritableSharedSpace()) return; |
| DCHECK(heap_object.InWritableSharedSpace()); |
| MemoryChunk* host_chunk = MemoryChunk::FromHeapObject(host); |
| DCHECK(host_chunk->InYoungGeneration()); |
| RememberedSet<OLD_TO_SHARED>::Insert<AccessMode::NON_ATOMIC>( |
| host_chunk, slot.address()); |
| collector_->MarkRootObject(Root::kClientHeap, heap_object); |
| } |
| |
| MarkCompactCollector* const collector_; |
| }; |
| |
| class InternalizedStringTableCleaner final : public RootVisitor { |
| public: |
| explicit InternalizedStringTableCleaner(Heap* heap) : heap_(heap) {} |
| |
| void VisitRootPointers(Root root, const char* description, |
| FullObjectSlot start, FullObjectSlot end) override { |
| UNREACHABLE(); |
| } |
| |
| void VisitRootPointers(Root root, const char* description, |
| OffHeapObjectSlot start, |
| OffHeapObjectSlot end) override { |
| DCHECK_EQ(root, Root::kStringTable); |
| // Visit all HeapObject pointers in [start, end). |
| auto* marking_state = heap_->marking_state(); |
| Isolate* isolate = heap_->isolate(); |
| for (OffHeapObjectSlot p = start; p < end; ++p) { |
| Object o = p.load(isolate); |
| if (o.IsHeapObject()) { |
| HeapObject heap_object = HeapObject::cast(o); |
| DCHECK(!Heap::InYoungGeneration(heap_object)); |
| if (!heap_object.InReadOnlySpace() && |
| marking_state->IsUnmarked(heap_object)) { |
| pointers_removed_++; |
| // Set the entry to the_hole_value (as deleted). |
| p.store(StringTable::deleted_element()); |
| } |
| } |
| } |
| } |
| |
| int PointersRemoved() const { return pointers_removed_; } |
| |
| private: |
| Heap* heap_; |
| int pointers_removed_ = 0; |
| }; |
| |
| enum class ExternalStringTableCleaningMode { kAll, kYoungOnly }; |
| |
| template <ExternalStringTableCleaningMode mode> |
| class ExternalStringTableCleaner : public RootVisitor { |
| public: |
| explicit ExternalStringTableCleaner(Heap* heap) : heap_(heap) {} |
| |
| void VisitRootPointers(Root root, const char* description, |
| FullObjectSlot start, FullObjectSlot end) override { |
| // Visit all HeapObject pointers in [start, end). |
| DCHECK_EQ(static_cast<int>(root), |
| static_cast<int>(Root::kExternalStringsTable)); |
| NonAtomicMarkingState* marking_state = heap_->non_atomic_marking_state(); |
| Object the_hole = ReadOnlyRoots(heap_).the_hole_value(); |
| for (FullObjectSlot p = start; p < end; ++p) { |
| Object o = *p; |
| if (!o.IsHeapObject()) continue; |
| HeapObject heap_object = HeapObject::cast(o); |
| // MinorMC doesn't update the young strings set and so it may contain |
| // strings that are already in old space. |
| if (!marking_state->IsUnmarked(heap_object)) continue; |
| if ((mode == ExternalStringTableCleaningMode::kYoungOnly) && |
| !Heap::InYoungGeneration(heap_object)) |
| continue; |
| if (o.IsExternalString()) { |
| heap_->FinalizeExternalString(String::cast(o)); |
| } else { |
| // The original external string may have been internalized. |
| DCHECK(o.IsThinString()); |
| } |
| // Set the entry to the_hole_value (as deleted). |
| p.store(the_hole); |
| } |
| } |
| |
| private: |
| Heap* heap_; |
| }; |
| |
| #ifdef V8_ENABLE_SANDBOX |
| class MarkExternalPointerFromExternalStringTable : public RootVisitor { |
| public: |
| explicit MarkExternalPointerFromExternalStringTable( |
| ExternalPointerTable* shared_table) |
| : visitor(shared_table) {} |
| |
| void VisitRootPointers(Root root, const char* description, |
| FullObjectSlot start, FullObjectSlot end) override { |
| // Visit all HeapObject pointers in [start, end). |
| for (FullObjectSlot p = start; p < end; ++p) { |
| Object o = *p; |
| if (o.IsHeapObject()) { |
| HeapObject heap_object = HeapObject::cast(o); |
| if (heap_object.IsExternalString()) { |
| ExternalString string = ExternalString::cast(heap_object); |
| string.VisitExternalPointers(&visitor); |
| } else { |
| // The original external string may have been internalized. |
| DCHECK(o.IsThinString()); |
| } |
| } |
| } |
| } |
| |
| private: |
| class MarkExternalPointerTableVisitor : public ObjectVisitor { |
| public: |
| explicit MarkExternalPointerTableVisitor(ExternalPointerTable* table) |
| : table_(table) {} |
| void VisitExternalPointer(HeapObject host, ExternalPointerSlot slot, |
| ExternalPointerTag tag) override { |
| DCHECK_NE(tag, kExternalPointerNullTag); |
| DCHECK(IsSharedExternalPointerType(tag)); |
| ExternalPointerHandle handle = slot.Relaxed_LoadHandle(); |
| table_->Mark(handle, slot.address()); |
| } |
| void VisitPointers(HeapObject host, ObjectSlot start, |
| ObjectSlot end) override { |
| UNREACHABLE(); |
| } |
| void VisitPointers(HeapObject host, MaybeObjectSlot start, |
| MaybeObjectSlot end) override { |
| UNREACHABLE(); |
| } |
| void VisitCodePointer(Code host, CodeObjectSlot slot) override { |
| UNREACHABLE(); |
| } |
| void VisitCodeTarget(InstructionStream host, RelocInfo* rinfo) override { |
| UNREACHABLE(); |
| } |
| void VisitEmbeddedPointer(InstructionStream host, |
| RelocInfo* rinfo) override { |
| UNREACHABLE(); |
| } |
| |
| private: |
| ExternalPointerTable* table_; |
| }; |
| |
| MarkExternalPointerTableVisitor visitor; |
| }; |
| #endif |
| |
| // Implementation of WeakObjectRetainer for mark compact GCs. All marked objects |
| // are retained. |
| class MarkCompactWeakObjectRetainer : public WeakObjectRetainer { |
| public: |
| explicit MarkCompactWeakObjectRetainer(MarkingState* marking_state) |
| : marking_state_(marking_state) {} |
| |
| Object RetainAs(Object object) override { |
| HeapObject heap_object = HeapObject::cast(object); |
| if (marking_state_->IsMarked(heap_object)) { |
| return object; |
| } else if (object.IsAllocationSite() && |
| !(AllocationSite::cast(object).IsZombie())) { |
| // "dead" AllocationSites need to live long enough for a traversal of new |
| // space. These sites get a one-time reprieve. |
| |
| Object nested = object; |
| while (nested.IsAllocationSite()) { |
| AllocationSite current_site = AllocationSite::cast(nested); |
| // MarkZombie will override the nested_site, read it first before |
| // marking |
| nested = current_site.nested_site(); |
| current_site.MarkZombie(); |
| marking_state_->TryMarkAndAccountLiveBytes(current_site); |
| } |
| |
| return object; |
| } else { |
| return Object(); |
| } |
| } |
| |
| private: |
| MarkingState* const marking_state_; |
| }; |
| |
| class RecordMigratedSlotVisitor : public ObjectVisitorWithCageBases { |
| public: |
| explicit RecordMigratedSlotVisitor( |
| Heap* heap, EphemeronRememberedSet* ephemeron_remembered_set) |
| : ObjectVisitorWithCageBases(heap->isolate()), |
| heap_(heap), |
| ephemeron_remembered_set_(ephemeron_remembered_set) {} |
| |
| inline void VisitPointer(HeapObject host, ObjectSlot p) final { |
| DCHECK(!HasWeakHeapObjectTag(p.load(cage_base()))); |
| RecordMigratedSlot(host, MaybeObject::FromObject(p.load(cage_base())), |
| p.address()); |
| } |
| |
| inline void VisitMapPointer(HeapObject host) final { |
| VisitPointer(host, host.map_slot()); |
| } |
| |
| inline void VisitPointer(HeapObject host, MaybeObjectSlot p) final { |
| DCHECK(!MapWord::IsPacked(p.Relaxed_Load(cage_base()).ptr())); |
| RecordMigratedSlot(host, p.load(cage_base()), p.address()); |
| } |
| |
| inline void VisitPointers(HeapObject host, ObjectSlot start, |
| ObjectSlot end) final { |
| while (start < end) { |
| VisitPointer(host, start); |
| ++start; |
| } |
| } |
| |
| inline void VisitPointers(HeapObject host, MaybeObjectSlot start, |
| MaybeObjectSlot end) final { |
| while (start < end) { |
| VisitPointer(host, start); |
| ++start; |
| } |
| } |
| |
| inline void VisitCodePointer(Code host, CodeObjectSlot slot) final { |
| // This code is similar to the implementation of VisitPointer() modulo |
| // new kind of slot. |
| DCHECK(!HasWeakHeapObjectTag(slot.load(code_cage_base()))); |
| Object code = slot.load(code_cage_base()); |
| RecordMigratedSlot(host, MaybeObject::FromObject(code), slot.address()); |
| } |
| |
| inline void VisitEphemeron(HeapObject host, int index, ObjectSlot key, |
| ObjectSlot value) override { |
| DCHECK(host.IsEphemeronHashTable()); |
| DCHECK(!Heap::InYoungGeneration(host)); |
| |
| if (v8_flags.minor_mc) { |
| // Minor MC lacks support for specialized generational ephemeron barriers. |
| // The regular write barrier works as well but keeps more memory alive. |
| // TODO(v8:12612): Add support to MinorMC. |
| ObjectVisitorWithCageBases::VisitEphemeron(host, index, key, value); |
| return; |
| } |
| |
| VisitPointer(host, value); |
| |
| if (ephemeron_remembered_set_ && Heap::InYoungGeneration(*key)) { |
| auto table = EphemeronHashTable::unchecked_cast(host); |
| auto insert_result = |
| ephemeron_remembered_set_->insert({table, std::unordered_set<int>()}); |
| insert_result.first->second.insert(index); |
| } else { |
| VisitPointer(host, key); |
| } |
| } |
| |
| inline void VisitCodeTarget(InstructionStream host, |
| RelocInfo* rinfo) override { |
| DCHECK(RelocInfo::IsCodeTargetMode(rinfo->rmode())); |
| InstructionStream target = |
| InstructionStream::FromTargetAddress(rinfo->target_address()); |
| // The target is always in old space, we don't have to record the slot in |
| // the old-to-new remembered set. |
| DCHECK(!Heap::InYoungGeneration(target)); |
| DCHECK(!target.InWritableSharedSpace()); |
| heap_->mark_compact_collector()->RecordRelocSlot(host, rinfo, target); |
| } |
| |
| inline void VisitEmbeddedPointer(InstructionStream host, |
| RelocInfo* rinfo) override { |
| DCHECK(RelocInfo::IsEmbeddedObjectMode(rinfo->rmode())); |
| HeapObject object = rinfo->target_object(cage_base()); |
| GenerationalBarrierForCode(host, rinfo, object); |
| WriteBarrier::Shared(host, rinfo, object); |
| heap_->mark_compact_collector()->RecordRelocSlot(host, rinfo, object); |
| } |
| |
| // Entries that are skipped for recording. |
| inline void VisitExternalReference(InstructionStream host, |
| RelocInfo* rinfo) final {} |
| inline void VisitInternalReference(InstructionStream host, |
| RelocInfo* rinfo) final {} |
| inline void VisitExternalPointer(HeapObject host, ExternalPointerSlot slot, |
| ExternalPointerTag tag) final {} |
| |
| protected: |
| inline void RecordMigratedSlot(HeapObject host, MaybeObject value, |
| Address slot) { |
| if (value->IsStrongOrWeak()) { |
| BasicMemoryChunk* p = BasicMemoryChunk::FromAddress(value.ptr()); |
| if (p->InYoungGeneration()) { |
| DCHECK_IMPLIES(p->IsToPage(), |
| v8_flags.minor_mc || |
| p->IsFlagSet(Page::PAGE_NEW_NEW_PROMOTION) || |
| p->IsLargePage()); |
| |
| MemoryChunk* chunk = MemoryChunk::FromHeapObject(host); |
| DCHECK(chunk->SweepingDone()); |
| RememberedSet<OLD_TO_NEW>::Insert<AccessMode::NON_ATOMIC>(chunk, slot); |
| } else if (p->IsEvacuationCandidate()) { |
| if (p->IsFlagSet(MemoryChunk::IS_EXECUTABLE)) { |
| RememberedSet<OLD_TO_CODE>::Insert<AccessMode::NON_ATOMIC>( |
| MemoryChunk::FromHeapObject(host), slot); |
| } else { |
| RememberedSet<OLD_TO_OLD>::Insert<AccessMode::NON_ATOMIC>( |
| MemoryChunk::FromHeapObject(host), slot); |
| } |
| } else if (p->InWritableSharedSpace() && !host.InWritableSharedSpace()) { |
| RememberedSet<OLD_TO_SHARED>::Insert<AccessMode::NON_ATOMIC>( |
| MemoryChunk::FromHeapObject(host), slot); |
| } |
| } |
| } |
| |
| Heap* const heap_; |
| EphemeronRememberedSet* ephemeron_remembered_set_; |
| }; |
| |
| class MigrationObserver { |
| public: |
| explicit MigrationObserver(Heap* heap) : heap_(heap) {} |
| |
| virtual ~MigrationObserver() = default; |
| virtual void Move(AllocationSpace dest, HeapObject src, HeapObject dst, |
| int size) = 0; |
| |
| protected: |
| Heap* heap_; |
| }; |
| |
| class ProfilingMigrationObserver final : public MigrationObserver { |
| public: |
| explicit ProfilingMigrationObserver(Heap* heap) : MigrationObserver(heap) {} |
| |
| inline void Move(AllocationSpace dest, HeapObject src, HeapObject dst, |
| int size) final { |
| // Note this method is called in a concurrent setting. The current object |
| // (src and dst) is somewhat safe to access without precautions, but other |
| // objects may be subject to concurrent modification. |
| if (dest == CODE_SPACE) { |
| PROFILE(heap_->isolate(), CodeMoveEvent(InstructionStream::cast(src), |
| InstructionStream::cast(dst))); |
| } else if (dest == OLD_SPACE && dst.IsBytecodeArray()) { |
| PROFILE(heap_->isolate(), BytecodeMoveEvent(BytecodeArray::cast(src), |
| BytecodeArray::cast(dst))); |
| } |
| heap_->OnMoveEvent(src, dst, size); |
| } |
| }; |
| |
| class HeapObjectVisitor { |
| public: |
| virtual ~HeapObjectVisitor() = default; |
| virtual bool Visit(HeapObject object, int size) = 0; |
| }; |
| |
| class EvacuateVisitorBase : public HeapObjectVisitor { |
| public: |
| void AddObserver(MigrationObserver* observer) { |
| migration_function_ = RawMigrateObject<MigrationMode::kObserved>; |
| observers_.push_back(observer); |
| } |
| |
| #if DEBUG |
| void DisableAbortEvacuationAtAddress(MemoryChunk* chunk) { |
| abort_evacuation_at_address_ = chunk->area_end(); |
| } |
| |
| void SetUpAbortEvacuationAtAddress(MemoryChunk* chunk) { |
| if (v8_flags.stress_compaction || v8_flags.stress_compaction_random) { |
| // Stress aborting of evacuation by aborting ~5% of evacuation candidates |
| // when stress testing. |
| const double kFraction = 0.05; |
| |
| if (rng_->NextDouble() < kFraction) { |
| const double abort_evacuation_percentage = rng_->NextDouble(); |
| abort_evacuation_at_address_ = |
| chunk->area_start() + |
| abort_evacuation_percentage * chunk->area_size(); |
| return; |
| } |
| } |
| |
| abort_evacuation_at_address_ = chunk->area_end(); |
| } |
| #endif // DEBUG |
| |
| protected: |
| enum MigrationMode { kFast, kObserved }; |
| |
| PtrComprCageBase cage_base() { |
| #if V8_COMPRESS_POINTERS |
| return PtrComprCageBase{heap_->isolate()}; |
| #else |
| return PtrComprCageBase{}; |
| #endif // V8_COMPRESS_POINTERS |
| } |
| |
| using MigrateFunction = void (*)(EvacuateVisitorBase* base, HeapObject dst, |
| HeapObject src, int size, |
| AllocationSpace dest); |
| |
| template <MigrationMode mode> |
| static void RawMigrateObject(EvacuateVisitorBase* base, HeapObject dst, |
| HeapObject src, int size, AllocationSpace dest) { |
| Address dst_addr = dst.address(); |
| Address src_addr = src.address(); |
| PtrComprCageBase cage_base = base->cage_base(); |
| DCHECK(base->heap_->AllowedToBeMigrated(src.map(cage_base), src, dest)); |
| DCHECK_NE(dest, LO_SPACE); |
| DCHECK_NE(dest, CODE_LO_SPACE); |
| if (dest == OLD_SPACE) { |
| DCHECK_OBJECT_SIZE(size); |
| DCHECK(IsAligned(size, kTaggedSize)); |
| base->heap_->CopyBlock(dst_addr, src_addr, size); |
| if (mode != MigrationMode::kFast) { |
| base->ExecuteMigrationObservers(dest, src, dst, size); |
| } |
| // In case the object's map gets relocated during GC we load the old map |
| // here. This is fine since they store the same content. |
| dst.IterateFast(dst.map(cage_base), size, base->record_visitor_); |
| } else if (dest == SHARED_SPACE) { |
| DCHECK_OBJECT_SIZE(size); |
| DCHECK(IsAligned(size, kTaggedSize)); |
| base->heap_->CopyBlock(dst_addr, src_addr, size); |
| if (mode != MigrationMode::kFast) { |
| base->ExecuteMigrationObservers(dest, src, dst, size); |
| } |
| dst.IterateFast(dst.map(cage_base), size, base->record_visitor_); |
| } else if (dest == CODE_SPACE) { |
| DCHECK_CODEOBJECT_SIZE(size, base->heap_->code_space()); |
| base->heap_->CopyBlock(dst_addr, src_addr, size); |
| InstructionStream istream = InstructionStream::cast(dst); |
| istream.Relocate(dst_addr - src_addr); |
| if (mode != MigrationMode::kFast) { |
| base->ExecuteMigrationObservers(dest, src, dst, size); |
| } |
| // In case the object's map gets relocated during GC we load the old map |
| // here. This is fine since they store the same content. |
| dst.IterateFast(dst.map(cage_base), size, base->record_visitor_); |
| } else { |
| DCHECK_OBJECT_SIZE(size); |
| DCHECK(dest == NEW_SPACE); |
| base->heap_->CopyBlock(dst_addr, src_addr, size); |
| if (mode != MigrationMode::kFast) { |
| base->ExecuteMigrationObservers(dest, src, dst, size); |
| } |
| } |
| src.set_map_word_forwarded(dst, kRelaxedStore); |
| } |
| |
| EvacuateVisitorBase(Heap* heap, EvacuationAllocator* local_allocator, |
| ConcurrentAllocator* shared_old_allocator, |
| RecordMigratedSlotVisitor* record_visitor) |
| : heap_(heap), |
| local_allocator_(local_allocator), |
| shared_old_allocator_(shared_old_allocator), |
| record_visitor_(record_visitor), |
| shared_string_table_(v8_flags.shared_string_table && |
| heap->isolate()->has_shared_space()) { |
| migration_function_ = RawMigrateObject<MigrationMode::kFast>; |
| #if DEBUG |
| rng_.emplace(heap_->isolate()->fuzzer_rng()->NextInt64()); |
| #endif // DEBUG |
| } |
| |
| inline bool TryEvacuateObject(AllocationSpace target_space, HeapObject object, |
| int size, HeapObject* target_object) { |
| #if DEBUG |
| DCHECK_LE(abort_evacuation_at_address_, |
| MemoryChunk::FromHeapObject(object)->area_end()); |
| DCHECK_GE(abort_evacuation_at_address_, |
| MemoryChunk::FromHeapObject(object)->area_start()); |
| |
| if (V8_UNLIKELY(object.address() >= abort_evacuation_at_address_)) { |
| return false; |
| } |
| #endif // DEBUG |
| |
| Map map = object.map(cage_base()); |
| AllocationAlignment alignment = HeapObject::RequiredAlignment(map); |
| AllocationResult allocation; |
| if (target_space == OLD_SPACE && ShouldPromoteIntoSharedHeap(map)) { |
| if (heap_->isolate()->is_shared_space_isolate()) { |
| DCHECK_NULL(shared_old_allocator_); |
| allocation = local_allocator_->Allocate( |
| SHARED_SPACE, size, AllocationOrigin::kGC, alignment); |
| } else { |
| allocation = shared_old_allocator_->AllocateRaw(size, alignment, |
| AllocationOrigin::kGC); |
| } |
| } else { |
| allocation = local_allocator_->Allocate(target_space, size, |
| AllocationOrigin::kGC, alignment); |
| } |
| if (allocation.To(target_object)) { |
| MigrateObject(*target_object, object, size, target_space); |
| if (target_space == CODE_SPACE) { |
| MemoryChunk::FromHeapObject(*target_object) |
| ->GetCodeObjectRegistry() |
| ->RegisterNewlyAllocatedCodeObject((*target_object).address()); |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| inline bool ShouldPromoteIntoSharedHeap(Map map) { |
| if (shared_string_table_) { |
| return String::IsInPlaceInternalizableExcludingExternal( |
| map.instance_type()); |
| } |
| return false; |
| } |
| |
| inline void ExecuteMigrationObservers(AllocationSpace dest, HeapObject src, |
| HeapObject dst, int size) { |
| for (MigrationObserver* obs : observers_) { |
| obs->Move(dest, src, dst, size); |
| } |
| } |
| |
| inline void MigrateObject(HeapObject dst, HeapObject src, int size, |
| AllocationSpace dest) { |
| migration_function_(this, dst, src, size, dest); |
| } |
| |
| Heap* heap_; |
| EvacuationAllocator* local_allocator_; |
| ConcurrentAllocator* shared_old_allocator_; |
| RecordMigratedSlotVisitor* record_visitor_; |
| std::vector<MigrationObserver*> observers_; |
| MigrateFunction migration_function_; |
| const bool shared_string_table_; |
| #if DEBUG |
| Address abort_evacuation_at_address_{kNullAddress}; |
| #endif // DEBUG |
| base::Optional<base::RandomNumberGenerator> rng_; |
| }; |
| |
| class EvacuateNewSpaceVisitor final : public EvacuateVisitorBase { |
| public: |
| explicit EvacuateNewSpaceVisitor( |
| Heap* heap, EvacuationAllocator* local_allocator, |
| ConcurrentAllocator* shared_old_allocator, |
| RecordMigratedSlotVisitor* record_visitor, |
| PretenuringHandler::PretenuringFeedbackMap* local_pretenuring_feedback) |
| : EvacuateVisitorBase(heap, local_allocator, shared_old_allocator, |
| record_visitor), |
| buffer_(LocalAllocationBuffer::InvalidBuffer()), |
| promoted_size_(0), |
| semispace_copied_size_(0), |
| pretenuring_handler_(heap_->pretenuring_handler()), |
| local_pretenuring_feedback_(local_pretenuring_feedback), |
| is_incremental_marking_(heap->incremental_marking()->IsMarking()), |
| shortcut_strings_(!heap_->IsGCWithStack() || |
| v8_flags.shortcut_strings_with_stack) {} |
| |
| inline bool Visit(HeapObject object, int size) override { |
| if (TryEvacuateWithoutCopy(object)) return true; |
| HeapObject target_object; |
| |
| pretenuring_handler_->UpdateAllocationSite(object.map(), object, |
| local_pretenuring_feedback_); |
| |
| if (!TryEvacuateObject(OLD_SPACE, object, size, &target_object)) { |
| heap_->FatalProcessOutOfMemory( |
| "MarkCompactCollector: young object promotion failed"); |
| } |
| |
| promoted_size_ += size; |
| return true; |
| } |
| |
| intptr_t promoted_size() { return promoted_size_; } |
| intptr_t semispace_copied_size() { return semispace_copied_size_; } |
| |
| private: |
| inline bool TryEvacuateWithoutCopy(HeapObject object) { |
| DCHECK(!is_incremental_marking_); |
| |
| if (!shortcut_strings_) return false; |
| |
| Map map = object.map(); |
| |
| // Some objects can be evacuated without creating a copy. |
| if (map.visitor_id() == kVisitThinString) { |
| HeapObject actual = ThinString::cast(object).unchecked_actual(); |
| if (MarkCompactCollector::IsOnEvacuationCandidate(actual)) return false; |
| object.set_map_word_forwarded(actual, kRelaxedStore); |
| return true; |
| } |
| // TODO(mlippautz): Handle ConsString. |
| |
| return false; |
| } |
| |
| inline AllocationSpace AllocateTargetObject(HeapObject old_object, int size, |
| HeapObject* target_object) { |
| AllocationAlignment alignment = |
| HeapObject::RequiredAlignment(old_object.map()); |
| AllocationSpace space_allocated_in = NEW_SPACE; |
| AllocationResult allocation = local_allocator_->Allocate( |
| NEW_SPACE, size, AllocationOrigin::kGC, alignment); |
| if (allocation.IsFailure()) { |
| allocation = AllocateInOldSpace(size, alignment); |
| space_allocated_in = OLD_SPACE; |
| } |
| bool ok = allocation.To(target_object); |
| DCHECK(ok); |
| USE(ok); |
| return space_allocated_in; |
| } |
| |
| inline AllocationResult AllocateInOldSpace(int size_in_bytes, |
| AllocationAlignment alignment) { |
| AllocationResult allocation = local_allocator_->Allocate( |
| OLD_SPACE, size_in_bytes, AllocationOrigin::kGC, alignment); |
| if (allocation.IsFailure()) { |
| heap_->FatalProcessOutOfMemory( |
| "MarkCompactCollector: semi-space copy, fallback in old gen"); |
| } |
| return allocation; |
| } |
| |
| LocalAllocationBuffer buffer_; |
| intptr_t promoted_size_; |
| intptr_t semispace_copied_size_; |
| PretenuringHandler* const pretenuring_handler_; |
| PretenuringHandler::PretenuringFeedbackMap* local_pretenuring_feedback_; |
| bool is_incremental_marking_; |
| const bool shortcut_strings_; |
| }; |
| |
| template <PageEvacuationMode mode> |
| class EvacuateNewSpacePageVisitor final : public HeapObjectVisitor { |
| public: |
| explicit EvacuateNewSpacePageVisitor( |
| Heap* heap, RecordMigratedSlotVisitor* record_visitor, |
| PretenuringHandler::PretenuringFeedbackMap* local_pretenuring_feedback) |
| : heap_(heap), |
| record_visitor_(record_visitor), |
| moved_bytes_(0), |
| pretenuring_handler_(heap_->pretenuring_handler()), |
| local_pretenuring_feedback_(local_pretenuring_feedback) {} |
| |
| static void Move(Page* page) { |
| switch (mode) { |
| case NEW_TO_NEW: |
| DCHECK(!v8_flags.minor_mc); |
| page->heap()->new_space()->PromotePageInNewSpace(page); |
| break; |
| case NEW_TO_OLD: { |
| page->heap()->new_space()->PromotePageToOldSpace(page); |
| break; |
| } |
| } |
| } |
| |
| inline bool Visit(HeapObject object, int size) override { |
| if (mode == NEW_TO_NEW) { |
| DCHECK(!v8_flags.minor_mc); |
| pretenuring_handler_->UpdateAllocationSite(object.map(), object, |
| local_pretenuring_feedback_); |
| } else if (mode == NEW_TO_OLD) { |
| if (v8_flags.minor_mc) { |
| pretenuring_handler_->UpdateAllocationSite(object.map(), object, |
| local_pretenuring_feedback_); |
| } |
| DCHECK(!IsCodeSpaceObject(object)); |
| PtrComprCageBase cage_base = GetPtrComprCageBase(object); |
| object.IterateFast(cage_base, record_visitor_); |
| } |
| return true; |
| } |
| |
| intptr_t moved_bytes() { return moved_bytes_; } |
| void account_moved_bytes(intptr_t bytes) { moved_bytes_ += bytes; } |
| |
| private: |
| Heap* heap_; |
| RecordMigratedSlotVisitor* record_visitor_; |
| intptr_t moved_bytes_; |
| PretenuringHandler* const pretenuring_handler_; |
| PretenuringHandler::PretenuringFeedbackMap* local_pretenuring_feedback_; |
| }; |
| |
| class EvacuateOldSpaceVisitor final : public EvacuateVisitorBase { |
| public: |
| EvacuateOldSpaceVisitor(Heap* heap, EvacuationAllocator* local_allocator, |
| ConcurrentAllocator* shared_old_allocator, |
| RecordMigratedSlotVisitor* record_visitor) |
| : EvacuateVisitorBase(heap, local_allocator, shared_old_allocator, |
| record_visitor) {} |
| |
| inline bool Visit(HeapObject object, int size) override { |
| HeapObject target_object; |
| if (TryEvacuateObject(Page::FromHeapObject(object)->owner_identity(), |
| object, size, &target_object)) { |
| DCHECK(object.map_word(heap_->isolate(), kRelaxedLoad) |
| .IsForwardingAddress()); |
| return true; |
| } |
| return false; |
| } |
| }; |
| |
| class EvacuateRecordOnlyVisitor final : public HeapObjectVisitor { |
| public: |
| explicit EvacuateRecordOnlyVisitor(Heap* heap) |
| : heap_(heap) |
| #ifdef V8_COMPRESS_POINTERS |
| , |
| cage_base_(heap->isolate()) |
| #endif // V8_COMPRESS_POINTERS |
| { |
| } |
| |
| // The pointer compression cage base value used for decompression of all |
| // tagged values except references to InstructionStream objects. |
| V8_INLINE PtrComprCageBase cage_base() const { |
| #ifdef V8_COMPRESS_POINTERS |
| return cage_base_; |
| #else |
| return PtrComprCageBase{}; |
| #endif // V8_COMPRESS_POINTERS |
| } |
| |
| inline bool Visit(HeapObject object, int size) override { |
| RecordMigratedSlotVisitor visitor(heap_, &heap_->ephemeron_remembered_set_); |
| Map map = object.map(cage_base()); |
| // Instead of calling object.IterateFast(cage_base(), &visitor) here |
| // we can shortcut and use the precomputed size value passed to the visitor. |
| DCHECK_EQ(object.SizeFromMap(map), size); |
| object.IterateFast(map, size, &visitor); |
| return true; |
| } |
| |
| private: |
| Heap* heap_; |
| #ifdef V8_COMPRESS_POINTERS |
| const PtrComprCageBase cage_base_; |
| #endif // V8_COMPRESS_POINTERS |
| }; |
| |
| // static |
| bool MarkCompactCollector::IsUnmarkedHeapObject(Heap* heap, FullObjectSlot p) { |
| Object o = *p; |
| if (!o.IsHeapObject()) return false; |
| HeapObject heap_object = HeapObject::cast(o); |
| if (heap_object.InReadOnlySpace()) return false; |
| MarkCompactCollector* collector = heap->mark_compact_collector(); |
| if (V8_UNLIKELY(collector->uses_shared_heap_) && |
| !collector->is_shared_space_isolate_) { |
| if (heap_object.InWritableSharedSpace()) return false; |
| } |
| return collector->non_atomic_marking_state()->IsUnmarked(heap_object); |
| } |
| |
| // static |
| bool MarkCompactCollector::IsUnmarkedSharedHeapObject(Heap* heap, |
| FullObjectSlot p) { |
| Object o = *p; |
| if (!o.IsHeapObject()) return false; |
| HeapObject heap_object = HeapObject::cast(o); |
| Isolate* shared_space_isolate = heap->isolate()->shared_space_isolate(); |
| MarkCompactCollector* collector = |
| shared_space_isolate->heap()->mark_compact_collector(); |
| if (!heap_object.InWritableSharedSpace()) return false; |
| return collector->non_atomic_marking_state()->IsUnmarked(heap_object); |
| } |
| |
| void MarkCompactCollector::MarkRoots(RootVisitor* root_visitor) { |
| // Mark the heap roots including global variables, stack variables, |
| // etc., and all objects reachable from them. |
| heap()->IterateRoots( |
| root_visitor, |
| base::EnumSet<SkipRoot>{SkipRoot::kWeak, SkipRoot::kTracedHandles, |
| SkipRoot::kConservativeStack, |
| SkipRoot::kReadOnlyBuiltins}); |
| |
| MarkWaiterQueueNode(isolate()); |
| |
| // Custom marking for top optimized frame. |
| CustomRootBodyMarkingVisitor custom_root_body_visitor(this); |
| ProcessTopOptimizedFrame(&custom_root_body_visitor, isolate()); |
| |
| if (isolate()->is_shared_space_isolate()) { |
| isolate()->global_safepoint()->IterateClientIsolates([this]( |
| Isolate* client) { |
| ClientRootMarkingVisitor client_root_visitor(this); |
| client->heap()->IterateRoots( |
| &client_root_visitor, |
| base::EnumSet<SkipRoot>{SkipRoot::kWeak, SkipRoot::kConservativeStack, |
| SkipRoot::kReadOnlyBuiltins}); |
| ClientCustomRootBodyMarkingVisitor client_custom_root_body_visitor(this); |
| ProcessTopOptimizedFrame(&client_custom_root_body_visitor, client); |
| }); |
| } |
| } |
| |
| void MarkCompactCollector::MarkRootsFromConservativeStack( |
| RootVisitor* root_visitor) { |
| heap()->IterateConservativeStackRootsIncludingClients( |
| root_visitor, Heap::ScanStackMode::kComplete); |
| } |
| |
| void MarkCompactCollector::MarkObjectsFromClientHeaps() { |
| if (!isolate()->is_shared_space_isolate()) return; |
| |
| isolate()->global_safepoint()->IterateClientIsolates( |
| [collector = this](Isolate* client) { |
| collector->MarkObjectsFromClientHeap(client); |
| }); |
| } |
| |
| void MarkCompactCollector::MarkObjectsFromClientHeap(Isolate* client) { |
| // There is no OLD_TO_SHARED remembered set for the young generation. We |
| // therefore need to iterate each object and check whether it points into the |
| // shared heap. As an optimization and to avoid a second heap iteration in the |
| // "update pointers" phase, all pointers into the shared heap are recorded in |
| // the OLD_TO_SHARED remembered set as well. |
| SharedHeapObjectVisitor visitor(this); |
| |
| PtrComprCageBase cage_base(client); |
| Heap* heap = client->heap(); |
| |
| // Ensure new space is iterable. |
| heap->MakeHeapIterable(); |
| |
| if (heap->new_space()) { |
| std::unique_ptr<ObjectIterator> iterator = |
| heap->new_space()->GetObjectIterator(heap); |
| for (HeapObject obj = iterator->Next(); !obj.is_null(); |
| obj = iterator->Next()) { |
| obj.IterateFast(cage_base, &visitor); |
| } |
| } |
| |
| if (heap->new_lo_space()) { |
| std::unique_ptr<ObjectIterator> iterator = |
| heap->new_lo_space()->GetObjectIterator(heap); |
| for (HeapObject obj = iterator->Next(); !obj.is_null(); |
| obj = iterator->Next()) { |
| obj.IterateFast(cage_base, &visitor); |
| } |
| } |
| |
| // In the old generation we can simply use the OLD_TO_SHARED remembered set to |
| // find all incoming pointers into the shared heap. |
| OldGenerationMemoryChunkIterator chunk_iterator(heap); |
| |
| // Tracking OLD_TO_SHARED requires the write barrier. |
| DCHECK(!v8_flags.disable_write_barriers); |
| |
| for (MemoryChunk* chunk = chunk_iterator.next(); chunk; |
| chunk = chunk_iterator.next()) { |
| InvalidatedSlotsFilter filter = InvalidatedSlotsFilter::OldToShared( |
| chunk, InvalidatedSlotsFilter::LivenessCheck::kNo); |
| RememberedSet<OLD_TO_SHARED>::Iterate( |
| chunk, |
| [collector = this, cage_base, &filter](MaybeObjectSlot slot) { |
| if (!filter.IsValid(slot.address())) return REMOVE_SLOT; |
| MaybeObject obj = slot.Relaxed_Load(cage_base); |
| HeapObject heap_object; |
| |
| if (obj.GetHeapObject(&heap_object) && |
| heap_object.InWritableSharedSpace()) { |
| collector->MarkRootObject(Root::kClientHeap, heap_object); |
| return KEEP_SLOT; |
| } else { |
| return REMOVE_SLOT; |
| } |
| }, |
| SlotSet::FREE_EMPTY_BUCKETS); |
| chunk->ReleaseInvalidatedSlots<OLD_TO_SHARED>(); |
| |
| RememberedSet<OLD_TO_SHARED>::IterateTyped( |
| chunk, [collector = this, heap](SlotType slot_type, Address slot) { |
| HeapObject heap_object = |
| UpdateTypedSlotHelper::GetTargetObject(heap, slot_type, slot); |
| if (heap_object.InWritableSharedSpace()) { |
| collector->MarkRootObject(Root::kClientHeap, heap_object); |
| return KEEP_SLOT; |
| } else { |
| return REMOVE_SLOT; |
| } |
| }); |
| } |
| |
| MarkWaiterQueueNode(client); |
| |
| #ifdef V8_ENABLE_SANDBOX |
| DCHECK(IsSharedExternalPointerType(kExternalStringResourceTag)); |
| DCHECK(IsSharedExternalPointerType(kExternalStringResourceDataTag)); |
| // All ExternalString resources are stored in the shared external pointer |
| // table. Mark entries from client heaps. |
| ExternalPointerTable& shared_table = client->shared_external_pointer_table(); |
| MarkExternalPointerFromExternalStringTable external_string_visitor( |
| &shared_table); |
| heap->external_string_table_.IterateAll(&external_string_visitor); |
| #endif // V8_ENABLE_SANDBOX |
| } |
| |
| void MarkCompactCollector::MarkWaiterQueueNode(Isolate* isolate) { |
| #ifdef V8_COMPRESS_POINTERS |
| DCHECK(IsSharedExternalPointerType(kWaiterQueueNodeTag)); |
| // Custom marking for the external pointer table entry used to hold the |
| // isolates' WaiterQueueNode, which is used by JS mutexes and condition |
| // variables. |
| ExternalPointerHandle* handle_location = |
| isolate->GetWaiterQueueNodeExternalPointerHandleLocation(); |
| ExternalPointerTable& shared_table = isolate->shared_external_pointer_table(); |
| ExternalPointerHandle handle = |
| base::AsAtomic32::Relaxed_Load(handle_location); |
| if (handle) { |
| shared_table.Mark(handle, reinterpret_cast<Address>(handle_location)); |
| } |
| #endif // V8_COMPRESS_POINTERS |
| } |
| |
| bool MarkCompactCollector::MarkTransitiveClosureUntilFixpoint() { |
| int iterations = 0; |
| int max_iterations = v8_flags.ephemeron_fixpoint_iterations; |
| |
| bool another_ephemeron_iteration_main_thread; |
| |
| do { |
| PerformWrapperTracing(); |
| |
| if (iterations >= max_iterations) { |
| // Give up fixpoint iteration and switch to linear algorithm. |
| return false; |
| } |
| |
| // Move ephemerons from next_ephemerons into current_ephemerons to |
| // drain them in this iteration. |
| DCHECK( |
| local_weak_objects()->current_ephemerons_local.IsLocalAndGlobalEmpty()); |
| weak_objects_.current_ephemerons.Merge(weak_objects_.next_ephemerons); |
| heap()->concurrent_marking()->set_another_ephemeron_iteration(false); |
| |
| { |
| TRACE_GC(heap()->tracer(), |
| GCTracer::Scope::MC_MARK_WEAK_CLOSURE_EPHEMERON_MARKING); |
| another_ephemeron_iteration_main_thread = ProcessEphemerons(); |
| } |
| |
| // Can only check for local emptiness here as parallel marking tasks may |
| // still be running. The caller performs the CHECKs for global emptiness. |
| CHECK(local_weak_objects()->current_ephemerons_local.IsLocalEmpty()); |
| CHECK(local_weak_objects()->discovered_ephemerons_local.IsLocalEmpty()); |
| |
| ++iterations; |
| } while (another_ephemeron_iteration_main_thread || |
| heap()->concurrent_marking()->another_ephemeron_iteration() || |
| !local_marking_worklists()->IsEmpty() || |
| !IsCppHeapMarkingFinished()); |
| |
| return true; |
| } |
| |
| bool MarkCompactCollector::ProcessEphemerons() { |
| Ephemeron ephemeron; |
| bool another_ephemeron_iteration = false; |
| |
| // Drain current_ephemerons and push ephemerons where key and value are still |
| // unreachable into next_ephemerons. |
| while (local_weak_objects()->current_ephemerons_local.Pop(&ephemeron)) { |
| if (ProcessEphemeron(ephemeron.key, ephemeron.value)) { |
| another_ephemeron_iteration = true; |
| } |
| } |
| |
| // Drain marking worklist and push discovered ephemerons into |
| // discovered_ephemerons. |
| size_t objects_processed; |
| std::tie(std::ignore, objects_processed) = ProcessMarkingWorklist(0); |
| |
| // As soon as a single object was processed and potentially marked another |
| // object we need another iteration. Otherwise we might miss to apply |
| // ephemeron semantics on it. |
| if (objects_processed > 0) another_ephemeron_iteration = true; |
| |
| // Drain discovered_ephemerons (filled in the drain MarkingWorklist-phase |
| // before) and push ephemerons where key and value are still unreachable into |
| // next_ephemerons. |
| while (local_weak_objects()->discovered_ephemerons_local.Pop(&ephemeron)) { |
| if (ProcessEphemeron(ephemeron.key, ephemeron.value)) { |
| another_ephemeron_iteration = true; |
| } |
| } |
| |
| // Flush local ephemerons for main task to global pool. |
| local_weak_objects()->ephemeron_hash_tables_local.Publish(); |
| local_weak_objects()->next_ephemerons_local.Publish(); |
| |
| return another_ephemeron_iteration; |
| } |
| |
| void MarkCompactCollector::MarkTransitiveClosureLinear() { |
| TRACE_GC(heap()->tracer(), |
| GCTracer::Scope::MC_MARK_WEAK_CLOSURE_EPHEMERON_LINEAR); |
| // This phase doesn't support parallel marking. |
| DCHECK(heap()->concurrent_marking()->IsStopped()); |
| std::unordered_multimap<HeapObject, HeapObject, Object::Hasher> key_to_values; |
| Ephemeron ephemeron; |
| |
| DCHECK( |
| local_weak_objects()->current_ephemerons_local.IsLocalAndGlobalEmpty()); |
| weak_objects_.current_ephemerons.Merge(weak_objects_.next_ephemerons); |
| while (local_weak_objects()->current_ephemerons_local.Pop(&ephemeron)) { |
| ProcessEphemeron(ephemeron.key, ephemeron.value); |
| |
| if (non_atomic_marking_state()->IsUnmarked(ephemeron.value)) { |
| key_to_values.insert(std::make_pair(ephemeron.key, ephemeron.value)); |
| } |
| } |
| |
| ephemeron_marking_.newly_discovered_limit = key_to_values.size(); |
| bool work_to_do = true; |
| |
| while (work_to_do) { |
| PerformWrapperTracing(); |
| |
| ResetNewlyDiscovered(); |
| ephemeron_marking_.newly_discovered_limit = key_to_values.size(); |
| |
| { |
| TRACE_GC(heap()->tracer(), |
| GCTracer::Scope::MC_MARK_WEAK_CLOSURE_EPHEMERON_MARKING); |
| // Drain marking worklist and push all discovered objects into |
| // newly_discovered. |
| ProcessMarkingWorklist( |
| 0, MarkingWorklistProcessingMode::kTrackNewlyDiscoveredObjects); |
| } |
| |
| while (local_weak_objects()->discovered_ephemerons_local.Pop(&ephemeron)) { |
| ProcessEphemeron(ephemeron.key, ephemeron.value); |
| |
| if (non_atomic_marking_state()->IsUnmarked(ephemeron.value)) { |
| key_to_values.insert(std::make_pair(ephemeron.key, ephemeron.value)); |
| } |
| } |
| |
| if (ephemeron_marking_.newly_discovered_overflowed) { |
| // If newly_discovered was overflowed just visit all ephemerons in |
| // next_ephemerons. |
| local_weak_objects()->next_ephemerons_local.Publish(); |
| weak_objects_.next_ephemerons.Iterate([&](Ephemeron ephemeron) { |
| if (non_atomic_marking_state()->IsMarked(ephemeron.key) && |
| non_atomic_marking_state()->TryMark(ephemeron.value)) { |
| local_marking_worklists()->Push(ephemeron.value); |
| } |
| }); |
| |
| } else { |
| // This is the good case: newly_discovered stores all discovered |
| // objects. Now use key_to_values to see if discovered objects keep more |
| // objects alive due to ephemeron semantics. |
| for (HeapObject object : ephemeron_marking_.newly_discovered) { |
| auto range = key_to_values.equal_range(object); |
| for (auto it = range.first; it != range.second; ++it) { |
| HeapObject value = it->second; |
| MarkObject(object, value); |
| } |
| } |
| } |
| |
| // Do NOT drain marking worklist here, otherwise the current checks |
| // for work_to_do are not sufficient for determining if another iteration |
| // is necessary. |
| |
| work_to_do = |
| !local_marking_worklists()->IsEmpty() || !IsCppHeapMarkingFinished(); |
| CHECK(local_weak_objects() |
| ->discovered_ephemerons_local.IsLocalAndGlobalEmpty()); |
| } |
| |
| ResetNewlyDiscovered(); |
| ephemeron_marking_.newly_discovered.shrink_to_fit(); |
| |
| CHECK(local_marking_worklists()->IsEmpty()); |
| |
| CHECK(weak_objects_.current_ephemerons.IsEmpty()); |
| CHECK(weak_objects_.discovered_ephemerons.IsEmpty()); |
| |
| // Flush local ephemerons for main task to global pool. |
| local_weak_objects()->ephemeron_hash_tables_local.Publish(); |
| local_weak_objects()->next_ephemerons_local.Publish(); |
| } |
| |
| void MarkCompactCollector::PerformWrapperTracing() { |
| auto* cpp_heap = CppHeap::From(heap_->cpp_heap()); |
| if (!cpp_heap) return; |
| |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_MARK_EMBEDDER_TRACING); |
| cpp_heap->AdvanceTracing(std::numeric_limits<double>::infinity()); |
| } |
| |
| std::pair<size_t, size_t> MarkCompactCollector::ProcessMarkingWorklist( |
| size_t bytes_to_process) { |
| return ProcessMarkingWorklist(bytes_to_process, |
| MarkingWorklistProcessingMode::kDefault); |
| } |
| |
| std::pair<size_t, size_t> MarkCompactCollector::ProcessMarkingWorklist( |
| size_t bytes_to_process, MarkingWorklistProcessingMode mode) { |
| HeapObject object; |
| size_t bytes_processed = 0; |
| size_t objects_processed = 0; |
| bool is_per_context_mode = local_marking_worklists()->IsPerContextMode(); |
| Isolate* isolate = heap()->isolate(); |
| PtrComprCageBase cage_base(isolate); |
| CodePageHeaderModificationScope rwx_write_scope( |
| "Marking of InstructionStream objects require write access to " |
| "Code page headers"); |
| if (parallel_marking_) |
| heap_->concurrent_marking()->RescheduleJobIfNeeded( |
| GarbageCollector::MARK_COMPACTOR, TaskPriority::kUserBlocking); |
| |
| while (local_marking_worklists()->Pop(&object) || |
| local_marking_worklists()->PopOnHold(&object)) { |
| // Left trimming may result in grey or black filler objects on the marking |
| // worklist. Ignore these objects. |
| if (object.IsFreeSpaceOrFiller(cage_base)) { |
| // Due to copying mark bits and the fact that grey and black have their |
| // first bit set, one word fillers are always black. |
| DCHECK_IMPLIES(object.map(cage_base) == |
| ReadOnlyRoots(isolate).one_pointer_filler_map(), |
| marking_state()->IsMarked(object)); |
| // Other fillers may be black or grey depending on the color of the object |
| // that was trimmed. |
| DCHECK_IMPLIES(object.map(cage_base) != |
| ReadOnlyRoots(isolate).one_pointer_filler_map(), |
| marking_state()->IsMarked(object)); |
| continue; |
| } |
| DCHECK(object.IsHeapObject()); |
| DCHECK(heap()->Contains(object)); |
| DCHECK(!(marking_state()->IsUnmarked(object))); |
| if (mode == MarkCompactCollector::MarkingWorklistProcessingMode:: |
| kTrackNewlyDiscoveredObjects) { |
| AddNewlyDiscovered(object); |
| } |
| Map map = object.map(cage_base); |
| 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 = marking_visitor_->Visit(map, object); |
| if (visited_size) { |
| 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); |
| } |
| bytes_processed += visited_size; |
| objects_processed++; |
| if (bytes_to_process && bytes_processed >= bytes_to_process) { |
| break; |
| } |
| } |
| return std::make_pair(bytes_processed, objects_processed); |
| } |
| |
| bool MarkCompactCollector::ProcessEphemeron(HeapObject key, HeapObject value) { |
| // Objects in the shared heap are prohibited from being used as keys in |
| // WeakMaps and WeakSets and therefore cannot be ephemeron keys, because that |
| // would enable thread local -> shared heap edges. |
| DCHECK(!key.InWritableSharedSpace()); |
| // Usually values that should not be marked are not added to the ephemeron |
| // worklist. However, minor collection during incremental marking may promote |
| // strings from the younger generation into the shared heap. This |
| // ShouldMarkObject call catches those cases. |
| if (!ShouldMarkObject(value)) return false; |
| 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; |
| } |
| |
| void MarkCompactCollector::VerifyEphemeronMarking() { |
| #ifdef VERIFY_HEAP |
| if (v8_flags.verify_heap) { |
| Ephemeron ephemeron; |
| |
| DCHECK( |
| local_weak_objects()->current_ephemerons_local.IsLocalAndGlobalEmpty()); |
| weak_objects_.current_ephemerons.Merge(weak_objects_.next_ephemerons); |
| while (local_weak_objects()->current_ephemerons_local.Pop(&ephemeron)) { |
| CHECK(!ProcessEphemeron(ephemeron.key, ephemeron.value)); |
| } |
| } |
| #endif // VERIFY_HEAP |
| } |
| |
| void MarkCompactCollector::MarkTransitiveClosure() { |
| // Incremental marking might leave ephemerons in main task's local |
| // buffer, flush it into global pool. |
| local_weak_objects()->next_ephemerons_local.Publish(); |
| |
| if (!MarkTransitiveClosureUntilFixpoint()) { |
| // Fixpoint iteration needed too many iterations and was cancelled. Use the |
| // guaranteed linear algorithm. But only in the final single-thread marking |
| // phase. |
| if (!parallel_marking_) MarkTransitiveClosureLinear(); |
| } |
| } |
| |
| void MarkCompactCollector::ProcessTopOptimizedFrame(ObjectVisitor* visitor, |
| Isolate* isolate) { |
| for (StackFrameIterator it(isolate, isolate->thread_local_top()); !it.done(); |
| it.Advance()) { |
| if (it.frame()->is_unoptimized()) return; |
| if (it.frame()->is_optimized()) { |
| GcSafeCode lookup_result = it.frame()->GcSafeLookupCode(); |
| if (!lookup_result.has_instruction_stream()) return; |
| if (!lookup_result.CanDeoptAt(isolate, it.frame()->pc())) { |
| InstructionStream istream = InstructionStream::unchecked_cast( |
| lookup_result.raw_instruction_stream()); |
| PtrComprCageBase cage_base(isolate); |
| InstructionStream::BodyDescriptor::IterateBody(istream.map(cage_base), |
| istream, visitor); |
| } |
| return; |
| } |
| } |
| } |
| |
| void MarkCompactCollector::RecordObjectStats() { |
| if (V8_LIKELY(!TracingFlags::is_gc_stats_enabled())) return; |
| // Cannot run during bootstrapping due to incomplete objects. |
| if (isolate()->bootstrapper()->IsActive()) return; |
| TRACE_EVENT0(TRACE_GC_CATEGORIES, "V8.GC_OBJECT_DUMP_STATISTICS"); |
| heap()->CreateObjectStats(); |
| ObjectStatsCollector collector(heap(), heap()->live_object_stats_.get(), |
| heap()->dead_object_stats_.get()); |
| collector.Collect(); |
| if (V8_UNLIKELY(TracingFlags::gc_stats.load(std::memory_order_relaxed) & |
| v8::tracing::TracingCategoryObserver::ENABLED_BY_TRACING)) { |
| std::stringstream live, dead; |
| heap()->live_object_stats_->Dump(live); |
| heap()->dead_object_stats_->Dump(dead); |
| TRACE_EVENT_INSTANT2(TRACE_DISABLED_BY_DEFAULT("v8.gc_stats"), |
| "V8.GC_Objects_Stats", TRACE_EVENT_SCOPE_THREAD, |
| "live", TRACE_STR_COPY(live.str().c_str()), "dead", |
| TRACE_STR_COPY(dead.str().c_str())); |
| } |
| if (v8_flags.trace_gc_object_stats) { |
| heap()->live_object_stats_->PrintJSON("live"); |
| heap()->dead_object_stats_->PrintJSON("dead"); |
| } |
| heap()->live_object_stats_->CheckpointObjectStats(); |
| heap()->dead_object_stats_->ClearObjectStats(); |
| } |
| |
| namespace { |
| |
| bool ShouldRetainMap(MarkingState* marking_state, Map map, int age) { |
| if (age == 0) { |
| // The map has aged. Do not retain this map. |
| return false; |
| } |
| Object constructor = map.GetConstructor(); |
| if (!constructor.IsHeapObject() || |
| (!HeapObject::cast(constructor).InReadOnlySpace() && |
| marking_state->IsUnmarked(HeapObject::cast(constructor)))) { |
| // The constructor is dead, no new objects with this map can |
| // be created. Do not retain this map. |
| return false; |
| } |
| return true; |
| } |
| |
| } // namespace |
| |
| void MarkCompactCollector::RetainMaps() { |
| // Retaining maps increases the chances of reusing map transitions at some |
| // memory cost, hence disable it when trying to reduce memory footprint more |
| // aggressively. |
| const bool should_retain_maps = |
| !heap()->ShouldReduceMemory() && v8_flags.retain_maps_for_n_gc != 0; |
| |
| for (WeakArrayList retained_maps : heap()->FindAllRetainedMaps()) { |
| DCHECK_EQ(0, retained_maps.length() % 2); |
| for (int i = 0; i < retained_maps.length(); i += 2) { |
| MaybeObject value = retained_maps.Get(i); |
| HeapObject map_heap_object; |
| if (!value->GetHeapObjectIfWeak(&map_heap_object)) { |
| continue; |
| } |
| int age = retained_maps.Get(i + 1).ToSmi().value(); |
| int new_age; |
| Map map = Map::cast(map_heap_object); |
| if (should_retain_maps && marking_state()->IsUnmarked(map)) { |
| if (ShouldRetainMap(marking_state(), map, age)) { |
| if (marking_state()->TryMark(map)) { |
| local_marking_worklists()->Push(map); |
| } |
| if (V8_UNLIKELY(v8_flags.track_retaining_path)) { |
| heap_->AddRetainingRoot(Root::kRetainMaps, map); |
| } |
| } |
| Object prototype = map.prototype(); |
| if (age > 0 && prototype.IsHeapObject() && |
| (!HeapObject::cast(prototype).InReadOnlySpace() && |
| marking_state()->IsUnmarked(HeapObject::cast(prototype)))) { |
| // The prototype is not marked, age the map. |
| new_age = age - 1; |
| } else { |
| // The prototype and the constructor are marked, this map keeps only |
| // transition tree alive, not JSObjects. Do not age the map. |
| new_age = age; |
| } |
| } else { |
| new_age = v8_flags.retain_maps_for_n_gc; |
| } |
| // Compact the array and update the age. |
| if (new_age != age) { |
| retained_maps.Set(i + 1, MaybeObject::FromSmi(Smi::FromInt(new_age))); |
| } |
| } |
| } |
| } |
| |
| void MarkCompactCollector::MarkLiveObjects() { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_MARK); |
| |
| const bool was_marked_incrementally = |
| !heap_->incremental_marking()->IsStopped(); |
| if (was_marked_incrementally) { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_MARK_FINISH_INCREMENTAL); |
| auto* incremental_marking = heap_->incremental_marking(); |
| DCHECK(incremental_marking->IsMajorMarking()); |
| incremental_marking->Stop(); |
| MarkingBarrier::PublishAll(heap()); |
| } |
| |
| #ifdef DEBUG |
| DCHECK(state_ == PREPARE_GC); |
| state_ = MARK_LIVE_OBJECTS; |
| #endif |
| |
| if (heap_->cpp_heap()) { |
| CppHeap::From(heap_->cpp_heap()) |
| ->EnterFinalPause(heap_->embedder_stack_state_); |
| } |
| |
| RootMarkingVisitor root_visitor(this); |
| |
| { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_MARK_ROOTS); |
| MarkRoots(&root_visitor); |
| } |
| |
| { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_MARK_CLIENT_HEAPS); |
| MarkObjectsFromClientHeaps(); |
| } |
| |
| { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_MARK_RETAIN_MAPS); |
| RetainMaps(); |
| } |
| |
| if (v8_flags.parallel_marking) { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_MARK_FULL_CLOSURE_PARALLEL); |
| parallel_marking_ = true; |
| heap_->concurrent_marking()->RescheduleJobIfNeeded( |
| GarbageCollector::MARK_COMPACTOR, TaskPriority::kUserBlocking); |
| MarkTransitiveClosure(); |
| { |
| TRACE_GC(heap()->tracer(), |
| GCTracer::Scope::MC_MARK_FULL_CLOSURE_PARALLEL_JOIN); |
| FinishConcurrentMarking(); |
| } |
| parallel_marking_ = false; |
| } |
| |
| { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_MARK_ROOTS); |
| MarkRootsFromConservativeStack(&root_visitor); |
| } |
| |
| { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_MARK_FULL_CLOSURE); |
| // Complete the transitive closure single-threaded to avoid races with |
| // multiple threads when processing weak maps and embedder heaps. |
| CHECK(heap()->concurrent_marking()->IsStopped()); |
| MarkTransitiveClosure(); |
| CHECK(local_marking_worklists()->IsEmpty()); |
| CHECK( |
| local_weak_objects()->current_ephemerons_local.IsLocalAndGlobalEmpty()); |
| CHECK(local_weak_objects() |
| ->discovered_ephemerons_local.IsLocalAndGlobalEmpty()); |
| CHECK(IsCppHeapMarkingFinished()); |
| VerifyEphemeronMarking(); |
| } |
| |
| if (was_marked_incrementally) { |
| // Disable the marking barrier after concurrent/parallel marking has |
| // finished as it will reset page flags that share the same bitmap as |
| // the evacuation candidate bit. |
| MarkingBarrier::DeactivateAll(heap()); |
| heap()->isolate()->traced_handles()->SetIsMarking(false); |
| } |
| |
| epoch_++; |
| } |
| |
| namespace { |
| |
| class ParallelClearingJob final : public v8::JobTask { |
| public: |
| class ClearingItem { |
| public: |
| virtual ~ClearingItem() = default; |
| virtual void Run(JobDelegate* delegate) = 0; |
| }; |
| |
| ParallelClearingJob() = default; |
| ~ParallelClearingJob() override = default; |
| ParallelClearingJob(const ParallelClearingJob&) = delete; |
| ParallelClearingJob& operator=(const ParallelClearingJob&) = delete; |
| |
| // v8::JobTask overrides. |
| void Run(JobDelegate* delegate) override { |
| std::unique_ptr<ClearingItem> item; |
| { |
| base::MutexGuard guard(&items_mutex_); |
| item = std::move(items_.back()); |
| items_.pop_back(); |
| } |
| item->Run(delegate); |
| } |
| |
| size_t GetMaxConcurrency(size_t worker_count) const override { |
| base::MutexGuard guard(&items_mutex_); |
| return items_.size(); |
| } |
| |
| void Add(std::unique_ptr<ClearingItem> item) { |
| items_.push_back(std::move(item)); |
| } |
| |
| private: |
| mutable base::Mutex items_mutex_; |
| std::vector<std::unique_ptr<ClearingItem>> items_; |
| }; |
| |
| class ClearStringTableJobItem final : public ParallelClearingJob::ClearingItem { |
| public: |
| explicit ClearStringTableJobItem(Isolate* isolate) : isolate_(isolate) {} |
| |
| void Run(JobDelegate* delegate) final { |
| if (isolate_->OwnsStringTables()) { |
| TRACE_GC1(isolate_->heap()->tracer(), |
| GCTracer::Scope::MC_CLEAR_STRING_TABLE, |
| delegate->IsJoiningThread() ? ThreadKind::kMain |
| : ThreadKind::kBackground); |
| // Prune the string table removing all strings only pointed to by the |
| // string table. Cannot use string_table() here because the string |
| // table is marked. |
| StringTable* string_table = isolate_->string_table(); |
| InternalizedStringTableCleaner internalized_visitor(isolate_->heap()); |
| string_table->DropOldData(); |
| string_table->IterateElements(&internalized_visitor); |
| string_table->NotifyElementsRemoved( |
| internalized_visitor.PointersRemoved()); |
| } |
| } |
| |
| private: |
| Isolate* const isolate_; |
| }; |
| |
| class StringForwardingTableCleaner final { |
| public: |
| explicit StringForwardingTableCleaner(Heap* heap) |
| : heap_(heap), |
| isolate_(heap_->isolate()), |
| marking_state_(heap_->non_atomic_marking_state()) {} |
| |
| // Transition all strings in the forwarding table to |
| // ThinStrings/ExternalStrings and clear the table afterwards. |
| void TransitionStrings() { |
| DCHECK(!heap_->IsGCWithStack() || |
| v8_flags.transition_strings_during_gc_with_stack); |
| StringForwardingTable* forwarding_table = |
| isolate_->string_forwarding_table(); |
| forwarding_table->IterateElements( |
| [&](StringForwardingTable::Record* record) { |
| TransitionStrings(record); |
| }); |
| forwarding_table->Reset(); |
| } |
| |
| // When performing GC with a stack, we conservatively assume that |
| // the GC could have been triggered by optimized code. Optimized code |
| // assumes that flat strings don't transition during GCs, so we are not |
| // allowed to transition strings to ThinString/ExternalString in that |
| // case. |
| // Instead we mark forward objects to keep them alive and update entries |
| // of evacuated objects later. |
| void ProcessFullWithStack() { |
| DCHECK(heap_->IsGCWithStack() && |
| !v8_flags.transition_strings_during_gc_with_stack); |
| StringForwardingTable* forwarding_table = |
| isolate_->string_forwarding_table(); |
| forwarding_table->IterateElements( |
| [&](StringForwardingTable::Record* record) { |
| MarkForwardObject(record); |
| }); |
| } |
| |
| // For Minor MC we don't mark forward objects, because they are always |
| // in old generation (and thus considered live). |
| // We only need to delete non-live young objects. |
| void ProcessYoungObjects() { |
| DCHECK(v8_flags.always_use_string_forwarding_table); |
| StringForwardingTable* forwarding_table = |
| isolate_->string_forwarding_table(); |
| forwarding_table->IterateElements( |
| [&](StringForwardingTable::Record* record) { |
| ClearNonLiveYoungObjects(record); |
| }); |
| } |
| |
| private: |
| void MarkForwardObject(StringForwardingTable::Record* record) { |
| Object original = record->OriginalStringObject(isolate_); |
| if (!original.IsHeapObject()) { |
| DCHECK_EQ(original, StringForwardingTable::deleted_element()); |
| return; |
| } |
| String original_string = String::cast(original); |
| if (marking_state_->IsMarked(original_string)) { |
| Object forward = record->ForwardStringObjectOrHash(isolate_); |
| if (!forward.IsHeapObject() || |
| HeapObject::cast(forward).InReadOnlySpace()) { |
| return; |
| } |
| marking_state_->TryMarkAndAccountLiveBytes(HeapObject::cast(forward)); |
| } else { |
| DisposeExternalResource(record); |
| record->set_original_string(StringForwardingTable::deleted_element()); |
| } |
| } |
| |
| void ClearNonLiveYoungObjects(StringForwardingTable::Record* record) { |
| Object original = record->OriginalStringObject(isolate_); |
| if (!original.IsHeapObject()) { |
| DCHECK_EQ(original, StringForwardingTable::deleted_element()); |
| return; |
| } |
| String original_string = String::cast(original); |
| if (!Heap::InYoungGeneration(original_string)) return; |
| if (!marking_state_->IsMarked(original_string)) { |
| DisposeExternalResource(record); |
| record->set_original_string(StringForwardingTable::deleted_element()); |
| } |
| } |
| |
| void TransitionStrings(StringForwardingTable::Record* record) { |
| Object original = record->OriginalStringObject(isolate_); |
| if (!original.IsHeapObject()) { |
| DCHECK_EQ(original, StringForwardingTable::deleted_element()); |
| return; |
| } |
| if (marking_state_->IsMarked(HeapObject::cast(original))) { |
| String original_string = String::cast(original); |
| if (original_string.IsThinString()) { |
| original_string = ThinString::cast(original_string).actual(); |
| } |
| TryExternalize(original_string, record); |
| TryInternalize(original_string, record); |
| original_string.set_raw_hash_field(record->raw_hash(isolate_)); |
| } else { |
| DisposeExternalResource(record); |
| } |
| } |
| |
| void TryExternalize(String original_string, |
| StringForwardingTable::Record* record) { |
| // If the string is already external, dispose the resource. |
| if (original_string.IsExternalString()) { |
| record->DisposeUnusedExternalResource(original_string); |
| return; |
| } |
| |
| bool is_one_byte; |
| v8::String::ExternalStringResourceBase* external_resource = |
| record->external_resource(&is_one_byte); |
| if (external_resource == nullptr) return; |
| |
| if (is_one_byte) { |
| original_string.MakeExternalDuringGC( |
| isolate_, |
| reinterpret_cast<v8::String::ExternalOneByteStringResource*>( |
| external_resource)); |
| } else { |
| original_string.MakeExternalDuringGC( |
| isolate_, reinterpret_cast<v8::String::ExternalStringResource*>( |
| external_resource)); |
| } |
| } |
| |
| void TryInternalize(String original_string, |
| StringForwardingTable::Record* record) { |
| if (original_string.IsInternalizedString()) return; |
| Object forward = record->ForwardStringObjectOrHash(isolate_); |
| if (!forward.IsHeapObject()) { |
| return; |
| } |
| String forward_string = String::cast(forward); |
| |
| // Mark the forwarded string to keep it alive. |
| if (!forward_string.InReadOnlySpace()) { |
| marking_state_->TryMarkAndAccountLiveBytes(forward_string); |
| } |
| // Transition the original string to a ThinString and override the |
| // forwarding index with the correct hash. |
| original_string.MakeThin(isolate_, forward_string); |
| // Record the slot in the old-to-old remembered set. This is |
| // required as the internalized string could be relocated during |
| // compaction. |
| ObjectSlot slot = |
| ThinString::cast(original_string).RawField(ThinString::kActualOffset); |
| MarkCompactCollector::RecordSlot(original_string, slot, forward_string); |
| } |
| |
| // Dispose external resource, if it wasn't disposed already. |
| // We can have multiple entries of the same external resource in the string |
| // forwarding table (i.e. concurrent externalization of a string with the same |
| // resource), therefore we keep track of already disposed resources to not |
| // dispose a resource more than once. |
| void DisposeExternalResource(StringForwardingTable::Record* record) { |
| Address resource = record->ExternalResourceAddress(); |
| if (resource != kNullAddress && disposed_resources_.count(resource) == 0) { |
| record->DisposeExternalResource(); |
| disposed_resources_.insert(resource); |
| } |
| } |
| |
| Heap* const heap_; |
| Isolate* const isolate_; |
| NonAtomicMarkingState* const marking_state_; |
| std::unordered_set<Address> disposed_resources_; |
| }; |
| |
| } // namespace |
| |
| void MarkCompactCollector::ClearNonLiveReferences() { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_CLEAR); |
| |
| if (isolate()->OwnsStringTables()) { |
| TRACE_GC(heap()->tracer(), |
| GCTracer::Scope::MC_CLEAR_STRING_FORWARDING_TABLE); |
| // Clear string forwarding table. Live strings are transitioned to |
| // ThinStrings/ExternalStrings in the cleanup process, if this is a GC |
| // without stack. |
| // Clearing the string forwarding table must happen before clearing the |
| // string table, as entries in the forwarding table can keep internalized |
| // strings alive. |
| StringForwardingTableCleaner forwarding_table_cleaner(heap()); |
| if (!heap_->IsGCWithStack() || |
| v8_flags.transition_strings_during_gc_with_stack) { |
| forwarding_table_cleaner.TransitionStrings(); |
| } else { |
| forwarding_table_cleaner.ProcessFullWithStack(); |
| } |
| } |
| |
| auto clearing_job = std::make_unique<ParallelClearingJob>(); |
| clearing_job->Add(std::make_unique<ClearStringTableJobItem>(isolate())); |
| auto clearing_job_handle = V8::GetCurrentPlatform()->PostJob( |
| TaskPriority::kUserBlocking, std::move(clearing_job)); |
| |
| { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_CLEAR_EXTERNAL_STRING_TABLE); |
| ExternalStringTableCleaner<ExternalStringTableCleaningMode::kAll> |
| external_visitor(heap()); |
| heap()->external_string_table_.IterateAll(&external_visitor); |
| heap()->external_string_table_.CleanUpAll(); |
| } |
| |
| { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_CLEAR_WEAK_GLOBAL_HANDLES); |
| // We depend on `IterateWeakRootsForPhantomHandles()` being called before |
| // `ProcessOldCodeCandidates()` in order to identify flushed bytecode in the |
| // CPU profiler. |
| isolate()->global_handles()->IterateWeakRootsForPhantomHandles( |
| &IsUnmarkedHeapObject); |
| isolate()->traced_handles()->ResetDeadNodes(&IsUnmarkedHeapObject); |
| |
| if (isolate()->is_shared_space_isolate()) { |
| isolate()->global_safepoint()->IterateClientIsolates([](Isolate* client) { |
| client->global_handles()->IterateWeakRootsForPhantomHandles( |
| &IsUnmarkedSharedHeapObject); |
| // No need to reset traced handles since they are always strong. |
| }); |
| } |
| } |
| |
| { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_CLEAR_FLUSHABLE_BYTECODE); |
| // `ProcessFlushedBaselineCandidates()` must be called after |
| // `ProcessOldCodeCandidates()` so that we correctly set the code object on |
| // the JSFunction after flushing. |
| ProcessOldCodeCandidates(); |
| ProcessFlushedBaselineCandidates(); |
| } |
| |
| { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_CLEAR_FLUSHED_JS_FUNCTIONS); |
| ClearFlushedJsFunctions(); |
| } |
| |
| { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_CLEAR_WEAK_LISTS); |
| // Process the weak references. |
| MarkCompactWeakObjectRetainer mark_compact_object_retainer(marking_state()); |
| heap()->ProcessAllWeakReferences(&mark_compact_object_retainer); |
| } |
| |
| { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_CLEAR_MAPS); |
| // ClearFullMapTransitions must be called before weak references are |
| // cleared. |
| ClearFullMapTransitions(); |
| // Weaken recorded strong DescriptorArray objects. This phase can |
| // potentially move everywhere after `ClearFullMapTransitions()`. |
| WeakenStrongDescriptorArrays(); |
| } |
| { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_CLEAR_WEAK_REFERENCES); |
| ClearWeakReferences(); |
| ClearWeakCollections(); |
| ClearJSWeakRefs(); |
| } |
| |
| PROFILE(heap()->isolate(), WeakCodeClearEvent()); |
| |
| MarkDependentCodeForDeoptimization(); |
| |
| #ifdef V8_ENABLE_SANDBOX |
| { |
| TRACE_GC(heap()->tracer(), |
| GCTracer::Scope::MC_SWEEP_EXTERNAL_POINTER_TABLE); |
| // External pointer table sweeping needs to happen before evacuating live |
| // objects as it may perform table compaction, which requires objects to |
| // still be at the same location as during marking. |
| isolate()->external_pointer_table().SweepAndCompact(isolate()); |
| if (isolate()->owns_shareable_data()) { |
| isolate()->shared_external_pointer_table().SweepAndCompact(isolate()); |
| } |
| } |
| #endif // V8_ENABLE_SANDBOX |
| |
| { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_CLEAR_JOIN_JOB); |
| clearing_job_handle->Join(); |
| } |
| |
| DCHECK(weak_objects_.transition_arrays.IsEmpty()); |
| DCHECK(weak_objects_.weak_references.IsEmpty()); |
| DCHECK(weak_objects_.weak_objects_in_code.IsEmpty()); |
| DCHECK(weak_objects_.js_weak_refs.IsEmpty()); |
| DCHECK(weak_objects_.weak_cells.IsEmpty()); |
| DCHECK(weak_objects_.code_flushing_candidates.IsEmpty()); |
| DCHECK(weak_objects_.baseline_flushing_candidates.IsEmpty()); |
| DCHECK(weak_objects_.flushed_js_functions.IsEmpty()); |
| } |
| |
| void MarkCompactCollector::MarkDependentCodeForDeoptimization() { |
| std::pair<HeapObject, Code> weak_object_in_code; |
| while (local_weak_objects()->weak_objects_in_code_local.Pop( |
| &weak_object_in_code)) { |
| HeapObject object = weak_object_in_code.first; |
| Code code = weak_object_in_code.second; |
| if (!non_atomic_marking_state()->IsMarked(object) && |
| !code.embedded_objects_cleared()) { |
| if (!code.marked_for_deoptimization()) { |
| code.SetMarkedForDeoptimization(isolate(), "weak objects"); |
| have_code_to_deoptimize_ = true; |
| } |
| code.ClearEmbeddedObjects(heap_); |
| DCHECK(code.embedded_objects_cleared()); |
| } |
| } |
| } |
| |
| void MarkCompactCollector::ClearPotentialSimpleMapTransition(Map dead_target) { |
| DCHECK(non_atomic_marking_state()->IsUnmarked(dead_target)); |
| Object potential_parent = dead_target.constructor_or_back_pointer(); |
| if (potential_parent.IsMap()) { |
| Map parent = Map::cast(potential_parent); |
| DisallowGarbageCollection no_gc_obviously; |
| if (non_atomic_marking_state()->IsMarked(parent) && |
| TransitionsAccessor(isolate(), parent) |
| .HasSimpleTransitionTo(dead_target)) { |
| ClearPotentialSimpleMapTransition(parent, dead_target); |
| } |
| } |
| } |
| |
| void MarkCompactCollector::ClearPotentialSimpleMapTransition(Map map, |
| Map dead_target) { |
| DCHECK(!map.is_prototype_map()); |
| DCHECK(!dead_target.is_prototype_map()); |
| DCHECK_EQ(map.raw_transitions(), HeapObjectReference::Weak(dead_target)); |
| // Take ownership of the descriptor array. |
| int number_of_own_descriptors = map.NumberOfOwnDescriptors(); |
| DescriptorArray descriptors = map.instance_descriptors(isolate()); |
| if (descriptors == dead_target.instance_descriptors(isolate()) && |
| number_of_own_descriptors > 0) { |
| TrimDescriptorArray(map, descriptors); |
| DCHECK(descriptors.number_of_descriptors() == number_of_own_descriptors); |
| } |
| } |
| |
| void MarkCompactCollector::FlushBytecodeFromSFI( |
| SharedFunctionInfo shared_info) { |
| DCHECK(shared_info.HasBytecodeArray()); |
| |
| // Retain objects required for uncompiled data. |
| String inferred_name = shared_info.inferred_name(); |
| int start_position = shared_info.StartPosition(); |
| int end_position = shared_info.EndPosition(); |
| |
| shared_info.DiscardCompiledMetadata( |
| isolate(), [](HeapObject object, ObjectSlot slot, HeapObject target) { |
| RecordSlot(object, slot, target); |
| }); |
| |
| // The size of the bytecode array should always be larger than an |
| // UncompiledData object. |
| static_assert(BytecodeArray::SizeFor(0) >= |
| UncompiledDataWithoutPreparseData::kSize); |
| |
| // Replace bytecode array with an uncompiled data array. |
| HeapObject compiled_data = shared_info.GetBytecodeArray(isolate()); |
| Address compiled_data_start = compiled_data.address(); |
| int compiled_data_size = ALIGN_TO_ALLOCATION_ALIGNMENT(compiled_data.Size()); |
| MemoryChunk* chunk = MemoryChunk::FromAddress(compiled_data_start); |
| |
| // Clear any recorded slots for the compiled data as being invalid. |
| RememberedSet<OLD_TO_NEW>::RemoveRange( |
| chunk, compiled_data_start, compiled_data_start + compiled_data_size, |
| SlotSet::FREE_EMPTY_BUCKETS); |
| RememberedSet<OLD_TO_SHARED>::RemoveRange( |
| chunk, compiled_data_start, compiled_data_start + compiled_data_size, |
| SlotSet::FREE_EMPTY_BUCKETS); |
| RememberedSet<OLD_TO_OLD>::RemoveRange( |
| chunk, compiled_data_start, compiled_data_start + compiled_data_size, |
| SlotSet::FREE_EMPTY_BUCKETS); |
| |
| // Swap the map, using set_map_after_allocation to avoid verify heap checks |
| // which are not necessary since we are doing this during the GC atomic pause. |
| compiled_data.set_map_after_allocation( |
| ReadOnlyRoots(heap()).uncompiled_data_without_preparse_data_map(), |
| SKIP_WRITE_BARRIER); |
| |
| // Create a filler object for any left over space in the bytecode array. |
| if (!heap()->IsLargeObject(compiled_data)) { |
| const int aligned_filler_offset = |
| ALIGN_TO_ALLOCATION_ALIGNMENT(UncompiledDataWithoutPreparseData::kSize); |
| heap()->CreateFillerObjectAt( |
| compiled_data.address() + aligned_filler_offset, |
| compiled_data_size - aligned_filler_offset); |
| } |
| |
| // Initialize the uncompiled data. |
| UncompiledData uncompiled_data = UncompiledData::cast(compiled_data); |
| uncompiled_data.InitAfterBytecodeFlush( |
| inferred_name, start_position, end_position, |
| [](HeapObject object, ObjectSlot slot, HeapObject target) { |
| RecordSlot(object, slot, target); |
| }); |
| |
| // Mark the uncompiled data as black, and ensure all fields have already been |
| // marked. |
| DCHECK(!ShouldMarkObject(inferred_name) || |
| marking_state()->IsMarked(inferred_name)); |
| marking_state()->TryMarkAndAccountLiveBytes(uncompiled_data); |
| |
| // Use the raw function data setter to avoid validity checks, since we're |
| // performing the unusual task of decompiling. |
| shared_info.set_function_data(uncompiled_data, kReleaseStore); |
| DCHECK(!shared_info.is_compiled()); |
| } |
| |
| void MarkCompactCollector::ProcessOldCodeCandidates() { |
| DCHECK(v8_flags.flush_bytecode || v8_flags.flush_baseline_code || |
| weak_objects_.code_flushing_candidates.IsEmpty()); |
| SharedFunctionInfo flushing_candidate; |
| while (local_weak_objects()->code_flushing_candidates_local.Pop( |
| &flushing_candidate)) { |
| Code baseline_code; |
| InstructionStream baseline_istream; |
| HeapObject baseline_bytecode_or_interpreter_data; |
| if (v8_flags.flush_baseline_code && flushing_candidate.HasBaselineCode()) { |
| baseline_code = |
| Code::cast(flushing_candidate.function_data(kAcquireLoad)); |
| // Safe to do a relaxed load here since the Code was acquire-loaded. |
| baseline_istream = baseline_code.instruction_stream( |
| baseline_code.code_cage_base(isolate()), kRelaxedLoad); |
| baseline_bytecode_or_interpreter_data = |
| baseline_code.bytecode_or_interpreter_data(); |
| } |
| // During flushing a BytecodeArray is transformed into an UncompiledData in |
| // place. Seeing an UncompiledData here implies that another |
| // SharedFunctionInfo had a reference to the same BytecodeArray and flushed |
| // it before processing this candidate. This can happen when using |
| // CloneSharedFunctionInfo(). |
| bool bytecode_already_decompiled = |
| flushing_candidate.function_data(isolate(), kAcquireLoad) |
| .IsUncompiledData(isolate()) || |
| (!baseline_istream.is_null() && |
| baseline_bytecode_or_interpreter_data.IsUncompiledData(isolate())); |
| bool is_bytecode_live = !bytecode_already_decompiled && |
| non_atomic_marking_state()->IsMarked( |
| flushing_candidate.GetBytecodeArray(isolate())); |
| if (!baseline_istream.is_null()) { |
| if (non_atomic_marking_state()->IsMarked(baseline_istream)) { |
| // Currently baseline code holds bytecode array strongly and it is |
| // always ensured that bytecode is live if baseline code is live. Hence |
| // baseline code can safely load bytecode array without any additional |
| // checks. In future if this changes we need to update these checks to |
| // flush code if the bytecode is not live and also update baseline code |
| // to bailout if there is no bytecode. |
| DCHECK(is_bytecode_live); |
| |
| // Regardless of whether the Code is a Code or |
| // the InstructionStream itself, if the InstructionStream is live then |
| // the Code has to be live and will have been marked via |
| // the owning JSFunction. |
| DCHECK(non_atomic_marking_state()->IsMarked(baseline_code)); |
| } else if (is_bytecode_live || bytecode_already_decompiled) { |
| // Reset the function_data field to the BytecodeArray, InterpreterData, |
| // or UncompiledData found on the baseline code. We can skip this step |
| // if the BytecodeArray is not live and not already decompiled, because |
| // FlushBytecodeFromSFI below will set the function_data field. |
| flushing_candidate.set_function_data( |
| baseline_bytecode_or_interpreter_data, kReleaseStore); |
| } |
| } |
| |
| if (!is_bytecode_live) { |
| // If baseline code flushing is disabled we should only flush bytecode |
| // from functions that don't have baseline data. |
| DCHECK(v8_flags.flush_baseline_code || |
| !flushing_candidate.HasBaselineCode()); |
| |
| if (bytecode_already_decompiled) { |
| flushing_candidate.DiscardCompiledMetadata( |
| isolate(), |
| [](HeapObject object, ObjectSlot slot, HeapObject target) { |
| RecordSlot(object, slot, target); |
| }); |
| } else { |
| // If the BytecodeArray is dead, flush it, which will replace the field |
| // with an uncompiled data object. |
| FlushBytecodeFromSFI(flushing_candidate); |
| } |
| } |
| |
| // Now record the slot, which has either been updated to an uncompiled data, |
| // Baseline code or BytecodeArray which is still alive. |
| ObjectSlot slot = |
| flushing_candidate.RawField(SharedFunctionInfo::kFunctionDataOffset); |
| RecordSlot(flushing_candidate, slot, HeapObject::cast(*slot)); |
| } |
| } |
| |
| void MarkCompactCollector::ClearFlushedJsFunctions() { |
| DCHECK(v8_flags.flush_bytecode || |
| weak_objects_.flushed_js_functions.IsEmpty()); |
| JSFunction flushed_js_function; |
| while (local_weak_objects()->flushed_js_functions_local.Pop( |
| &flushed_js_function)) { |
| auto gc_notify_updated_slot = [](HeapObject object, ObjectSlot slot, |
| Object target) { |
| RecordSlot(object, slot, HeapObject::cast(target)); |
| }; |
| flushed_js_function.ResetIfCodeFlushed(gc_notify_updated_slot); |
| } |
| } |
| |
| void MarkCompactCollector::ProcessFlushedBaselineCandidates() { |
| DCHECK(v8_flags.flush_baseline_code || |
| weak_objects_.baseline_flushing_candidates.IsEmpty()); |
| JSFunction flushed_js_function; |
| while (local_weak_objects()->baseline_flushing_candidates_local.Pop( |
| &flushed_js_function)) { |
| auto gc_notify_updated_slot = [](HeapObject object, ObjectSlot slot, |
| Object target) { |
| RecordSlot(object, slot, HeapObject::cast(target)); |
| }; |
| flushed_js_function.ResetIfCodeFlushed(gc_notify_updated_slot); |
| |
| // Record the code slot that has been updated either to CompileLazy, |
| // InterpreterEntryTrampoline or baseline code. |
| ObjectSlot slot = flushed_js_function.RawField(JSFunction::kCodeOffset); |
| RecordSlot(flushed_js_function, slot, HeapObject::cast(*slot)); |
| } |
| } |
| |
| void MarkCompactCollector::ClearFullMapTransitions() { |
| TransitionArray array; |
| while (local_weak_objects()->transition_arrays_local.Pop(&array)) { |
| int num_transitions = array.number_of_entries(); |
| if (num_transitions > 0) { |
| Map map; |
| // The array might contain "undefined" elements because it's not yet |
| // filled. Allow it. |
| if (array.GetTargetIfExists(0, isolate(), &map)) { |
| DCHECK(!map.is_null()); // Weak pointers aren't cleared yet. |
| Object constructor_or_back_pointer = map.constructor_or_back_pointer(); |
| if (constructor_or_back_pointer.IsSmi()) { |
| DCHECK(isolate()->has_active_deserializer()); |
| DCHECK_EQ(constructor_or_back_pointer, |
| Smi::uninitialized_deserialization_value()); |
| continue; |
| } |
| Map parent = Map::cast(map.constructor_or_back_pointer()); |
| bool parent_is_alive = non_atomic_marking_state()->IsMarked(parent); |
| DescriptorArray descriptors = |
| parent_is_alive ? parent.instance_descriptors(isolate()) |
| : DescriptorArray(); |
| bool descriptors_owner_died = |
| CompactTransitionArray(parent, array, descriptors); |
| if (descriptors_owner_died) { |
| TrimDescriptorArray(parent, descriptors); |
| } |
| } |
| } |
| } |
| } |
| |
| // Returns false if no maps have died, or if the transition array is |
| // still being deserialized. |
| bool MarkCompactCollector::TransitionArrayNeedsCompaction( |
| TransitionArray transitions, int num_transitions) { |
| for (int i = 0; i < num_transitions; ++i) { |
| MaybeObject raw_target = transitions.GetRawTarget(i); |
| if (raw_target.IsSmi()) { |
| // This target is still being deserialized, |
| DCHECK(isolate()->has_active_deserializer()); |
| DCHECK_EQ(raw_target.ToSmi(), Smi::uninitialized_deserialization_value()); |
| #ifdef DEBUG |
| // Targets can only be dead iff this array is fully deserialized. |
| for (int j = 0; j < num_transitions; ++j) { |
| DCHECK_IMPLIES( |
| !transitions.GetRawTarget(j).IsSmi(), |
| !non_atomic_marking_state()->IsUnmarked(transitions.GetTarget(j))); |
| } |
| #endif |
| return false; |
| } else if (non_atomic_marking_state()->IsUnmarked( |
| TransitionsAccessor::GetTargetFromRaw(raw_target))) { |
| #ifdef DEBUG |
| // Targets can only be dead iff this array is fully deserialized. |
| for (int j = 0; j < num_transitions; ++j) { |
| DCHECK(!transitions.GetRawTarget(j).IsSmi()); |
| } |
| #endif |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool MarkCompactCollector::CompactTransitionArray(Map map, |
| TransitionArray transitions, |
| DescriptorArray descriptors) { |
| DCHECK(!map.is_prototype_map()); |
| int num_transitions = transitions.number_of_entries(); |
| if (!TransitionArrayNeedsCompaction(transitions, num_transitions)) { |
| return false; |
| } |
| bool descriptors_owner_died = false; |
| int transition_index = 0; |
| // Compact all live transitions to the left. |
| for (int i = 0; i < num_transitions; ++i) { |
| Map target = transitions.GetTarget(i); |
| DCHECK_EQ(target.constructor_or_back_pointer(), map); |
| if (non_atomic_marking_state()->IsUnmarked(target)) { |
| if (!descriptors.is_null() && |
| target.instance_descriptors(isolate()) == descriptors) { |
| DCHECK(!target.is_prototype_map()); |
| descriptors_owner_died = true; |
| } |
| } else { |
| if (i != transition_index) { |
| Name key = transitions.GetKey(i); |
| transitions.SetKey(transition_index, key); |
| HeapObjectSlot key_slot = transitions.GetKeySlot(transition_index); |
| RecordSlot(transitions, key_slot, key); |
| MaybeObject raw_target = transitions.GetRawTarget(i); |
| transitions.SetRawTarget(transition_index, raw_target); |
| HeapObjectSlot target_slot = |
| transitions.GetTargetSlot(transition_index); |
| RecordSlot(transitions, target_slot, raw_target->GetHeapObject()); |
| } |
| transition_index++; |
| } |
| } |
| // If there are no transitions to be cleared, return. |
| if (transition_index == num_transitions) { |
| DCHECK(!descriptors_owner_died); |
| return false; |
| } |
| // Note that we never eliminate a transition array, though we might right-trim |
| // such that number_of_transitions() == 0. If this assumption changes, |
| // TransitionArray::Insert() will need to deal with the case that a transition |
| // array disappeared during GC. |
| int trim = transitions.Capacity() - transition_index; |
| if (trim > 0) { |
| heap_->RightTrimWeakFixedArray(transitions, |
| trim * TransitionArray::kEntrySize); |
| transitions.SetNumberOfTransitions(transition_index); |
| } |
| return descriptors_owner_died; |
| } |
| |
| void MarkCompactCollector::RightTrimDescriptorArray(DescriptorArray array, |
| int descriptors_to_trim) { |
| int old_nof_all_descriptors = array.number_of_all_descriptors(); |
| int new_nof_all_descriptors = old_nof_all_descriptors - descriptors_to_trim; |
| DCHECK_LT(0, descriptors_to_trim); |
| DCHECK_LE(0, new_nof_all_descriptors); |
| Address start = array.GetDescriptorSlot(new_nof_all_descriptors).address(); |
| Address end = array.GetDescriptorSlot(old_nof_all_descriptors).address(); |
| MemoryChunk* chunk = MemoryChunk::FromHeapObject(array); |
| RememberedSet<OLD_TO_NEW>::RemoveRange(chunk, start, end, |
| SlotSet::FREE_EMPTY_BUCKETS); |
| RememberedSet<OLD_TO_SHARED>::RemoveRange(chunk, start, end, |
| SlotSet::FREE_EMPTY_BUCKETS); |
| RememberedSet<OLD_TO_OLD>::RemoveRange(chunk, start, end, |
| SlotSet::FREE_EMPTY_BUCKETS); |
| if (V8_COMPRESS_POINTERS_8GB_BOOL) { |
| Address aligned_start = ALIGN_TO_ALLOCATION_ALIGNMENT(start); |
| Address aligned_end = ALIGN_TO_ALLOCATION_ALIGNMENT(end); |
| if (aligned_start < aligned_end) { |
| heap()->CreateFillerObjectAt( |
| aligned_start, static_cast<int>(aligned_end - aligned_start)); |
| } |
| if (Heap::ShouldZapGarbage()) { |
| Address zap_end = std::min(aligned_start, end); |
| MemsetTagged(ObjectSlot(start), Object(static_cast<Address>(kZapValue)), |
| (zap_end - start) >> kTaggedSizeLog2); |
| } |
| } else { |
| heap()->CreateFillerObjectAt(start, static_cast<int>(end - start)); |
| } |
| array.set_number_of_all_descriptors(new_nof_all_descriptors); |
| } |
| |
| void MarkCompactCollector::RecordStrongDescriptorArraysForWeakening( |
| GlobalHandleVector<DescriptorArray> strong_descriptor_arrays) { |
| DCHECK(heap()->incremental_marking()->IsMajorMarking()); |
| base::MutexGuard guard(&strong_descriptor_arrays_mutex_); |
| strong_descriptor_arrays_.push_back(std::move(strong_descriptor_arrays)); |
| } |
| |
| void MarkCompactCollector::WeakenStrongDescriptorArrays() { |
| Map descriptor_array_map = ReadOnlyRoots(isolate()).descriptor_array_map(); |
| for (auto vec : strong_descriptor_arrays_) { |
| for (auto it = vec.begin(); it != vec.end(); ++it) { |
| DescriptorArray raw = it.raw(); |
| DCHECK(raw.IsStrongDescriptorArray()); |
| raw.set_map_safe_transition_no_write_barrier(descriptor_array_map); |
| DCHECK_EQ(raw.raw_gc_state(kRelaxedLoad), 0); |
| } |
| } |
| strong_descriptor_arrays_.clear(); |
| } |
| |
| void MarkCompactCollector::TrimDescriptorArray(Map map, |
| DescriptorArray descriptors) { |
| int number_of_own_descriptors = map.NumberOfOwnDescriptors(); |
| if (number_of_own_descriptors == 0) { |
| DCHECK(descriptors == ReadOnlyRoots(heap_).empty_descriptor_array()); |
| return; |
| } |
| int to_trim = |
| descriptors.number_of_all_descriptors() - number_of_own_descriptors; |
| if (to_trim > 0) { |
| descriptors.set_number_of_descriptors(number_of_own_descriptors); |
| RightTrimDescriptorArray(descriptors, to_trim); |
| |
| TrimEnumCache(map, descriptors); |
| descriptors.Sort(); |
| } |
| DCHECK(descriptors.number_of_descriptors() == number_of_own_descriptors); |
| map.set_owns_descriptors(true); |
| } |
| |
| void MarkCompactCollector::TrimEnumCache(Map map, DescriptorArray descriptors) { |
| int live_enum = map.EnumLength(); |
| if (live_enum == kInvalidEnumCacheSentinel) { |
| live_enum = map.NumberOfEnumerableProperties(); |
| } |
| if (live_enum == 0) return descriptors.ClearEnumCache(); |
| EnumCache enum_cache = descriptors.enum_cache(); |
| |
| FixedArray keys = enum_cache.keys(); |
| int to_trim = keys.length() - live_enum; |
| if (to_trim <= 0) return; |
| heap_->RightTrimFixedArray(keys, to_trim); |
| |
| FixedArray indices = enum_cache.indices(); |
| to_trim = indices.length() - live_enum; |
| if (to_trim <= 0) return; |
| heap_->RightTrimFixedArray(indices, to_trim); |
| } |
| |
| void MarkCompactCollector::ClearWeakCollections() { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_CLEAR_WEAK_COLLECTIONS); |
| EphemeronHashTable table; |
| while (local_weak_objects()->ephemeron_hash_tables_local.Pop(&table)) { |
| for (InternalIndex i : table.IterateEntries()) { |
| HeapObject key = HeapObject::cast(table.KeyAt(i)); |
| #ifdef VERIFY_HEAP |
| if (v8_flags.verify_heap) { |
| Object value = table.ValueAt(i); |
| if (value.IsHeapObject()) { |
| HeapObject heap_object = HeapObject::cast(value); |
| CHECK_IMPLIES(!ShouldMarkObject(key) || |
| non_atomic_marking_state()->IsMarked(key), |
| !ShouldMarkObject(heap_object) || |
| non_atomic_marking_state()->IsMarked(heap_object)); |
| } |
| } |
| #endif // VERIFY_HEAP |
| if (!ShouldMarkObject(key)) continue; |
| if (!non_atomic_marking_state()->IsMarked(key)) { |
| table.RemoveEntry(i); |
| } |
| } |
| } |
| for (auto it = heap_->ephemeron_remembered_set_.begin(); |
| it != heap_->ephemeron_remembered_set_.end();) { |
| if (!non_atomic_marking_state()->IsMarked(it->first)) { |
| it = heap_->ephemeron_remembered_set_.erase(it); |
| } else { |
| ++it; |
| } |
| } |
| } |
| |
| void MarkCompactCollector::ClearWeakReferences() { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_CLEAR_WEAK_REFERENCES); |
| std::pair<HeapObject, HeapObjectSlot> slot; |
| HeapObjectReference cleared_weak_ref = |
| HeapObjectReference::ClearedValue(isolate()); |
| while (local_weak_objects()->weak_references_local.Pop(&slot)) { |
| HeapObject value; |
| // The slot could have been overwritten, so we have to treat it |
| // as MaybeObjectSlot. |
| MaybeObjectSlot location(slot.second); |
| if ((*location)->GetHeapObjectIfWeak(&value)) { |
| DCHECK(!value.IsCell()); |
| if (value.InReadOnlySpace() || |
| non_atomic_marking_state()->IsMarked(value)) { |
| // The value of the weak reference is alive. |
| RecordSlot(slot.first, HeapObjectSlot(location), value); |
| } else { |
| if (value.IsMap()) { |
| // The map is non-live. |
| ClearPotentialSimpleMapTransition(Map::cast(value)); |
| } |
| location.store(cleared_weak_ref); |
| } |
| } |
| } |
| } |
| |
| void MarkCompactCollector::ClearJSWeakRefs() { |
| JSWeakRef weak_ref; |
| while (local_weak_objects()->js_weak_refs_local.Pop(&weak_ref)) { |
| HeapObject target = HeapObject::cast(weak_ref.target()); |
| if (!target.InReadOnlySpace() && |
| !non_atomic_marking_state()->IsMarked(target)) { |
| weak_ref.set_target(ReadOnlyRoots(isolate()).undefined_value()); |
| } else { |
| // The value of the JSWeakRef is alive. |
| ObjectSlot slot = weak_ref.RawField(JSWeakRef::kTargetOffset); |
| RecordSlot(weak_ref, slot, target); |
| } |
| } |
| WeakCell weak_cell; |
| while (local_weak_objects()->weak_cells_local.Pop(&weak_cell)) { |
| auto gc_notify_updated_slot = [](HeapObject object, ObjectSlot slot, |
| Object target) { |
| if (target.IsHeapObject()) { |
| RecordSlot(object, slot, HeapObject::cast(target)); |
| } |
| }; |
| HeapObject target = HeapObject::cast(weak_cell.target()); |
| if (!target.InReadOnlySpace() && |
| !non_atomic_marking_state()->IsMarked(target)) { |
| DCHECK(target.CanBeHeldWeakly()); |
| // The value of the WeakCell is dead. |
| JSFinalizationRegistry finalization_registry = |
| JSFinalizationRegistry::cast(weak_cell.finalization_registry()); |
| if (!finalization_registry.scheduled_for_cleanup()) { |
| heap()->EnqueueDirtyJSFinalizationRegistry(finalization_registry, |
| gc_notify_updated_slot); |
| } |
| // We're modifying the pointers in WeakCell and JSFinalizationRegistry |
| // during GC; thus we need to record the slots it writes. The normal write |
| // barrier is not enough, since it's disabled before GC. |
| weak_cell.Nullify(isolate(), gc_notify_updated_slot); |
| DCHECK(finalization_registry.NeedsCleanup()); |
| DCHECK(finalization_registry.scheduled_for_cleanup()); |
| } else { |
| // The value of the WeakCell is alive. |
| ObjectSlot slot = weak_cell.RawField(WeakCell::kTargetOffset); |
| RecordSlot(weak_cell, slot, HeapObject::cast(*slot)); |
| } |
| |
| HeapObject unregister_token = weak_cell.unregister_token(); |
| if (!unregister_token.InReadOnlySpace() && |
| !non_atomic_marking_state()->IsMarked(unregister_token)) { |
| DCHECK(unregister_token.CanBeHeldWeakly()); |
| // The unregister token is dead. Remove any corresponding entries in the |
| // key map. Multiple WeakCell with the same token will have all their |
| // unregister_token field set to undefined when processing the first |
| // WeakCell. Like above, we're modifying pointers during GC, so record the |
| // slots. |
| JSFinalizationRegistry finalization_registry = |
| JSFinalizationRegistry::cast(weak_cell.finalization_registry()); |
| finalization_registry.RemoveUnregisterToken( |
| unregister_token, isolate(), |
| JSFinalizationRegistry::kKeepMatchedCellsInRegistry, |
| gc_notify_updated_slot); |
| } else { |
| // The unregister_token is alive. |
| ObjectSlot slot = weak_cell.RawField(WeakCell::kUnregisterTokenOffset); |
| RecordSlot(weak_cell, slot, HeapObject::cast(*slot)); |
| } |
| } |
| heap()->PostFinalizationRegistryCleanupTaskIfNeeded(); |
| } |
| |
| bool MarkCompactCollector::IsOnEvacuationCandidate(MaybeObject obj) { |
| return Page::FromAddress(obj.ptr())->IsEvacuationCandidate(); |
| } |
| |
| // static |
| bool MarkCompactCollector::ShouldRecordRelocSlot(InstructionStream host, |
| RelocInfo* rinfo, |
| HeapObject target) { |
| MemoryChunk* source_chunk = MemoryChunk::FromHeapObject(host); |
| BasicMemoryChunk* target_chunk = BasicMemoryChunk::FromHeapObject(target); |
| return target_chunk->IsEvacuationCandidate() && |
| !source_chunk->ShouldSkipEvacuationSlotRecording(); |
| } |
| |
| // static |
| MarkCompactCollector::RecordRelocSlotInfo |
| MarkCompactCollector::ProcessRelocInfo(InstructionStream host, RelocInfo* rinfo, |
| HeapObject target) { |
| RecordRelocSlotInfo result; |
| const RelocInfo::Mode rmode = rinfo->rmode(); |
| Address addr; |
| SlotType slot_type; |
| |
| if (rinfo->IsInConstantPool()) { |
| addr = rinfo->constant_pool_entry_address(); |
| |
| if (RelocInfo::IsCodeTargetMode(rmode)) { |
| slot_type = SlotType::kConstPoolCodeEntry; |
| } else if (RelocInfo::IsCompressedEmbeddedObject(rmode)) { |
| slot_type = SlotType::kConstPoolEmbeddedObjectCompressed; |
| } else { |
| DCHECK(RelocInfo::IsFullEmbeddedObject(rmode)); |
| slot_type = SlotType::kConstPoolEmbeddedObjectFull; |
| } |
| } else { |
| addr = rinfo->pc(); |
| |
| if (RelocInfo::IsCodeTargetMode(rmode)) { |
| slot_type = SlotType::kCodeEntry; |
| } else if (RelocInfo::IsFullEmbeddedObject(rmode)) { |
| slot_type = SlotType::kEmbeddedObjectFull; |
| } else { |
| DCHECK(RelocInfo::IsCompressedEmbeddedObject(rmode)); |
| slot_type = SlotType::kEmbeddedObjectCompressed; |
| } |
| } |
| |
| MemoryChunk* const source_chunk = MemoryChunk::FromHeapObject(host); |
| const uintptr_t offset = addr - source_chunk->address(); |
| DCHECK_LT(offset, static_cast<uintptr_t>(TypedSlotSet::kMaxOffset)); |
| result.memory_chunk = source_chunk; |
| result.slot_type = slot_type; |
| result.offset = static_cast<uint32_t>(offset); |
| |
| return result; |
| } |
| |
| // static |
| void MarkCompactCollector::RecordRelocSlot(InstructionStream host, |
| RelocInfo* rinfo, |
| HeapObject target) { |
| if (!ShouldRecordRelocSlot(host, rinfo, target)) return; |
| RecordRelocSlotInfo info = ProcessRelocInfo(host, rinfo, target); |
| |
| // Access to TypeSlots need to be protected, since LocalHeaps might |
| // publish code in the background thread. |
| base::Optional<base::MutexGuard> opt_guard; |
| if (v8_flags.concurrent_sparkplug) { |
| opt_guard.emplace(info.memory_chunk->mutex()); |
| } |
| RememberedSet<OLD_TO_OLD>::InsertTyped(info.memory_chunk, info.slot_type, |
| info.offset); |
| } |
| |
| namespace { |
| |
| // Missing specialization MakeSlotValue<FullObjectSlot, WEAK>() will turn |
| // attempt to store a weak reference to strong-only slot to a compilation error. |
| template <typename TSlot, HeapObjectReferenceType reference_type> |
| typename TSlot::TObject MakeSlotValue(HeapObject heap_object); |
| |
| template <> |
| Object MakeSlotValue<ObjectSlot, HeapObjectReferenceType::STRONG>( |
| HeapObject heap_object) { |
| return heap_object; |
| } |
| |
| template <> |
| MaybeObject MakeSlotValue<MaybeObjectSlot, HeapObjectReferenceType::STRONG>( |
| HeapObject heap_object) { |
| return HeapObjectReference::Strong(heap_object); |
| } |
| |
| template <> |
| MaybeObject MakeSlotValue<MaybeObjectSlot, HeapObjectReferenceType::WEAK>( |
| HeapObject heap_object) { |
| return HeapObjectReference::Weak(heap_object); |
| } |
| |
| template <> |
| Object MakeSlotValue<OffHeapObjectSlot, HeapObjectReferenceType::STRONG>( |
| HeapObject heap_object) { |
| return heap_object; |
| } |
| |
| #ifdef V8_COMPRESS_POINTERS |
| template <> |
| Object MakeSlotValue<FullObjectSlot, HeapObjectReferenceType::STRONG>( |
| HeapObject heap_object) { |
| return heap_object; |
| } |
| |
| template <> |
| MaybeObject MakeSlotValue<FullMaybeObjectSlot, HeapObjectReferenceType::STRONG>( |
| HeapObject heap_object) { |
| return HeapObjectReference::Strong(heap_object); |
| } |
| |
| #ifdef V8_EXTERNAL_CODE_SPACE |
| template <> |
| Object MakeSlotValue<CodeObjectSlot, HeapObjectReferenceType::STRONG>( |
| HeapObject heap_object) { |
| return heap_object; |
| } |
| #endif // V8_EXTERNAL_CODE_SPACE |
| |
| // The following specialization |
| // MakeSlotValue<FullMaybeObjectSlot, HeapObjectReferenceType::WEAK>() |
| // is not used. |
| #endif // V8_COMPRESS_POINTERS |
| |
| template <AccessMode access_mode, HeapObjectReferenceType reference_type, |
| typename TSlot> |
| static inline void UpdateSlot(PtrComprCageBase cage_base, TSlot slot, |
| typename TSlot::TObject old, |
| HeapObject heap_obj) { |
| static_assert(std::is_same<TSlot, FullObjectSlot>::value || |
| std::is_same<TSlot, ObjectSlot>::value || |
| std::is_same<TSlot, FullMaybeObjectSlot>::value || |
| std::is_same<TSlot, MaybeObjectSlot>::value || |
| std::is_same<TSlot, OffHeapObjectSlot>::value || |
| std::is_same<TSlot, CodeObjectSlot>::value, |
| "Only [Full|OffHeap]ObjectSlot, [Full]MaybeObjectSlot " |
| "or CodeObjectSlot are expected here"); |
| MapWord map_word = heap_obj.map_word(cage_base, kRelaxedLoad); |
| if (map_word.IsForwardingAddress()) { |
| DCHECK_IMPLIES((!v8_flags.minor_mc && !Heap::InFromPage(heap_obj)), |
| MarkCompactCollector::IsOnEvacuationCandidate(heap_obj) || |
| Page::FromHeapObject(heap_obj)->IsFlagSet( |
| Page::COMPACTION_WAS_ABORTED)); |
| typename TSlot::TObject target = MakeSlotValue<TSlot, reference_type>( |
| map_word.ToForwardingAddress(heap_obj)); |
| if (access_mode == AccessMode::NON_ATOMIC) { |
| // Needs to be atomic for map space compaction: This slot could be a map |
| // word which we update while loading the map word for updating the slot |
| // on another page. |
| slot.Relaxed_Store(target); |
| } else { |
| slot.Release_CompareAndSwap(old, target); |
| } |
| DCHECK(!Heap::InFromPage(target)); |
| DCHECK(!MarkCompactCollector::IsOnEvacuationCandidate(target)); |
| } else { |
| DCHECK(MapWord::IsMapOrForwarded(map_word.ToMap())); |
| } |
| } |
| |
| template <AccessMode access_mode, typename TSlot> |
| static inline void UpdateSlot(PtrComprCageBase cage_base, TSlot slot) { |
| typename TSlot::TObject obj = slot.Relaxed_Load(cage_base); |
| HeapObject heap_obj; |
| if (TSlot::kCanBeWeak && obj->GetHeapObjectIfWeak(&heap_obj)) { |
| UpdateSlot<access_mode, HeapObjectReferenceType::WEAK>(cage_base, slot, obj, |
| heap_obj); |
| } else if (obj->GetHeapObjectIfStrong(&heap_obj)) { |
| UpdateSlot<access_mode, HeapObjectReferenceType::STRONG>(cage_base, slot, |
| obj, heap_obj); |
| } |
| } |
| |
| static inline SlotCallbackResult UpdateOldToSharedSlot( |
| PtrComprCageBase cage_base, MaybeObjectSlot slot) { |
| MaybeObject obj = slot.Relaxed_Load(cage_base); |
| HeapObject heap_obj; |
| |
| if (obj.GetHeapObject(&heap_obj)) { |
| if (obj.IsWeak()) { |
| UpdateSlot<AccessMode::NON_ATOMIC, HeapObjectReferenceType::WEAK>( |
| cage_base, slot, obj, heap_obj); |
| } else { |
| UpdateSlot<AccessMode::NON_ATOMIC, HeapObjectReferenceType::STRONG>( |
| cage_base, slot, obj, heap_obj); |
| } |
| |
| return heap_obj.InWritableSharedSpace() ? KEEP_SLOT : REMOVE_SLOT; |
| } else { |
| return REMOVE_SLOT; |
| } |
| } |
| |
| template <AccessMode access_mode, typename TSlot> |
| static inline void UpdateStrongSlot(PtrComprCageBase cage_base, TSlot slot) { |
| typename TSlot::TObject obj = slot.Relaxed_Load(cage_base); |
| DCHECK(!HAS_WEAK_HEAP_OBJECT_TAG(obj.ptr())); |
| HeapObject heap_obj; |
| if (obj.GetHeapObject(&heap_obj)) { |
| UpdateSlot<access_mode, HeapObjectReferenceType::STRONG>(cage_base, slot, |
| obj, heap_obj); |
| } |
| } |
| |
| static inline SlotCallbackResult UpdateStrongOldToSharedSlot( |
| PtrComprCageBase cage_base, FullMaybeObjectSlot slot) { |
| MaybeObject obj = slot.Relaxed_Load(cage_base); |
| DCHECK(!HAS_WEAK_HEAP_OBJECT_TAG(obj.ptr())); |
| HeapObject heap_obj; |
| if (obj.GetHeapObject(&heap_obj)) { |
| UpdateSlot<AccessMode::NON_ATOMIC, HeapObjectReferenceType::STRONG>( |
| cage_base, slot, obj, heap_obj); |
| return heap_obj.InWritableSharedSpace() ? KEEP_SLOT : REMOVE_SLOT; |
| } |
| |
| return REMOVE_SLOT; |
| } |
| |
| template <AccessMode access_mode> |
| static inline void UpdateStrongCodeSlot(HeapObject host, |
| PtrComprCageBase cage_base, |
| PtrComprCageBase code_cage_base, |
| CodeObjectSlot slot) { |
| Object obj = slot.Relaxed_Load(code_cage_base); |
| DCHECK(!HAS_WEAK_HEAP_OBJECT_TAG(obj.ptr())); |
| HeapObject heap_obj; |
| if (obj.GetHeapObject(&heap_obj)) { |
| UpdateSlot<access_mode, HeapObjectReferenceType::STRONG>(cage_base, slot, |
| obj, heap_obj); |
| |
| Code code = Code::cast(HeapObject::FromAddress( |
| slot.address() - Code::kInstructionStreamOffset)); |
| InstructionStream instruction_stream = |
| code.instruction_stream(code_cage_base); |
| Isolate* isolate_for_sandbox = GetIsolateForSandbox(host); |
| code.UpdateInstructionStart(isolate_for_sandbox, instruction_stream); |
| } |
| } |
| |
| } // namespace |
| |
| // Visitor for updating root pointers and to-space pointers. |
| // It does not expect to encounter pointers to dead objects. |
| class PointersUpdatingVisitor final : public ObjectVisitorWithCageBases, |
| public RootVisitor { |
| public: |
| explicit PointersUpdatingVisitor(Heap* heap) |
| : ObjectVisitorWithCageBases(heap) {} |
| |
| void VisitPointer(HeapObject host, ObjectSlot p) override { |
| UpdateStrongSlotInternal(cage_base(), p); |
| } |
| |
| void VisitPointer(HeapObject host, MaybeObjectSlot p) override { |
| UpdateSlotInternal(cage_base(), p); |
| } |
| |
| void VisitPointers(HeapObject host, ObjectSlot start, |
| ObjectSlot end) override { |
| for (ObjectSlot p = start; p < end; ++p) { |
| UpdateStrongSlotInternal(cage_base(), p); |
| } |
| } |
| |
| void VisitPointers(HeapObject host, MaybeObjectSlot start, |
| MaybeObjectSlot end) final { |
| for (MaybeObjectSlot p = start; p < end; ++p) { |
| UpdateSlotInternal(cage_base(), p); |
| } |
| } |
| |
| void VisitCodePointer(Code host, CodeObjectSlot slot) override { |
| UpdateStrongCodeSlot<AccessMode::NON_ATOMIC>(host, cage_base(), |
| code_cage_base(), slot); |
| } |
| |
| void VisitRootPointer(Root root, const char* description, |
| FullObjectSlot p) override { |
| DCHECK(!MapWord::IsPacked(p.Relaxed_Load().ptr())); |
| UpdateRootSlotInternal(cage_base(), p); |
| } |
| |
| void VisitRootPointers(Root root, const char* description, |
| FullObjectSlot start, FullObjectSlot end) override { |
| for (FullObjectSlot p = start; p < end; ++p) { |
| UpdateRootSlotInternal(cage_base(), p); |
| } |
| } |
| |
| void VisitRootPointers(Root root, const char* description, |
| OffHeapObjectSlot start, |
| OffHeapObjectSlot end) override { |
| for (OffHeapObjectSlot p = start; p < end; ++p) { |
| UpdateRootSlotInternal(cage_base(), p); |
| } |
| } |
| |
| void VisitCodeTarget(InstructionStream host, RelocInfo* rinfo) override { |
| // This visitor nevers visits code objects. |
| UNREACHABLE(); |
| } |
| |
| void VisitEmbeddedPointer(InstructionStream host, RelocInfo* rinfo) override { |
| // This visitor nevers visits code objects. |
| UNREACHABLE(); |
| } |
| |
| private: |
| static inline void UpdateRootSlotInternal(PtrComprCageBase cage_base, |
| FullObjectSlot slot) { |
| UpdateStrongSlot<AccessMode::NON_ATOMIC>(cage_base, slot); |
| } |
| |
| static inline void UpdateRootSlotInternal(PtrComprCageBase cage_base, |
| OffHeapObjectSlot slot) { |
| UpdateStrongSlot<AccessMode::NON_ATOMIC>(cage_base, slot); |
| } |
| |
| static inline void UpdateStrongMaybeObjectSlotInternal( |
| PtrComprCageBase cage_base, MaybeObjectSlot slot) { |
| UpdateStrongSlot<AccessMode::NON_ATOMIC>(cage_base, slot); |
| } |
| |
| static inline void UpdateStrongSlotInternal(PtrComprCageBase cage_base, |
| ObjectSlot slot) { |
| UpdateStrongSlot<AccessMode::NON_ATOMIC>(cage_base, slot); |
| } |
| |
| static inline void UpdateSlotInternal(PtrComprCageBase cage_base, |
| MaybeObjectSlot slot) { |
| UpdateSlot<AccessMode::NON_ATOMIC>(cage_base, slot); |
| } |
| }; |
| |
| static String UpdateReferenceInExternalStringTableEntry(Heap* heap, |
| FullObjectSlot p) { |
| HeapObject old_string = HeapObject::cast(*p); |
| MapWord map_word = old_string.map_word(kRelaxedLoad); |
| |
| if (map_word.IsForwardingAddress()) { |
| String new_string = String::cast(map_word.ToForwardingAddress(old_string)); |
| |
| if (new_string.IsExternalString()) { |
| MemoryChunk::MoveExternalBackingStoreBytes( |
| ExternalBackingStoreType::kExternalString, |
| Page::FromAddress((*p).ptr()), Page::FromHeapObject(new_string), |
| ExternalString::cast(new_string).ExternalPayloadSize()); |
| } |
| return new_string; |
| } |
| |
| return String::cast(*p); |
| } |
| |
| void MarkCompactCollector::EvacuatePrologue() { |
| // New space. |
| NewSpace* new_space = heap()->new_space(); |
| |
| if (new_space) { |
| // Append the list of new space pages to be processed. |
| for (Page* p : *new_space) { |
| if (non_atomic_marking_state()->live_bytes(p) > 0) { |
| new_space_evacuation_pages_.push_back(p); |
| } |
| } |
| if (!v8_flags.minor_mc) |
| SemiSpaceNewSpace::From(new_space)->EvacuatePrologue(); |
| } |
| |
| if (heap()->new_lo_space()) { |
| heap()->new_lo_space()->Flip(); |
| heap()->new_lo_space()->ResetPendingObject(); |
| } |
| |
| // Old space. |
| DCHECK(old_space_evacuation_pages_.empty()); |
| old_space_evacuation_pages_ = std::move(evacuation_candidates_); |
| evacuation_candidates_.clear(); |
| DCHECK(evacuation_candidates_.empty()); |
| } |
| |
| #if DEBUG |
| namespace { |
| |
| void VerifyRememberedSetsAfterEvacuation(Heap* heap, |
| GarbageCollector collector) { |
| // Old-to-old slot sets must be empty after evacuation. |
| bool new_space_is_empty = |
| !heap->new_space() || heap->new_space()->Size() == 0; |
| DCHECK_IMPLIES(collector == GarbageCollector::MARK_COMPACTOR, |
| new_space_is_empty); |
| |
| MemoryChunkIterator chunk_iterator(heap); |
| |
| while (chunk_iterator.HasNext()) { |
| MemoryChunk* chunk = chunk_iterator.Next(); |
| |
| // Old-to-old slot sets must be empty after evacuation. |
| DCHECK_NULL((chunk->slot_set<OLD_TO_OLD, AccessMode::ATOMIC>())); |
| DCHECK_NULL((chunk->typed_slot_set<OLD_TO_OLD, AccessMode::ATOMIC>())); |
| |
| if (new_space_is_empty && (collector == GarbageCollector::MARK_COMPACTOR)) { |
| // Old-to-new slot sets must be empty after evacuation. |
| DCHECK_NULL((chunk->slot_set<OLD_TO_NEW, AccessMode::ATOMIC>())); |
| DCHECK_NULL((chunk->typed_slot_set<OLD_TO_NEW, AccessMode::ATOMIC>())); |
| } |
| |
| // Old-to-shared slots may survive GC but there should never be any slots in |
| // new or shared spaces. |
| AllocationSpace id = chunk->owner_identity(); |
| if (id == SHARED_SPACE || id == SHARED_LO_SPACE || id == NEW_SPACE || |
| id == NEW_LO_SPACE) { |
| DCHECK_NULL((chunk->slot_set<OLD_TO_SHARED, AccessMode::ATOMIC>())); |
| DCHECK_NULL((chunk->typed_slot_set<OLD_TO_SHARED, AccessMode::ATOMIC>())); |
| } |
| |
| // GCs need to filter invalidated slots. |
| DCHECK_NULL(chunk->invalidated_slots<OLD_TO_OLD>()); |
| DCHECK_NULL(chunk->invalidated_slots<OLD_TO_NEW>()); |
| if (collector == GarbageCollector::MARK_COMPACTOR) { |
| DCHECK_NULL(chunk->invalidated_slots<OLD_TO_SHARED>()); |
| } |
| } |
| } |
| |
| } // namespace |
| #endif // DEBUG |
| |
| void MarkCompactCollector::EvacuateEpilogue() { |
| aborted_evacuation_candidates_due_to_oom_.clear(); |
| aborted_evacuation_candidates_due_to_flags_.clear(); |
| |
| // New space. |
| if (heap()->new_space()) { |
| DCHECK_EQ(0, heap()->new_space()->Size()); |
| } |
| |
| // Old generation. Deallocate evacuated candidate pages. |
| ReleaseEvacuationCandidates(); |
| |
| #ifdef DEBUG |
| VerifyRememberedSetsAfterEvacuation(heap(), GarbageCollector::MARK_COMPACTOR); |
| #endif // DEBUG |
| } |
| |
| namespace { |
| ConcurrentAllocator* CreateSharedOldAllocator(Heap* heap) { |
| if (v8_flags.shared_string_table && heap->isolate()->has_shared_space() && |
| !heap->isolate()->is_shared_space_isolate()) { |
| return new ConcurrentAllocator(nullptr, heap->shared_allocation_space(), |
| ConcurrentAllocator::Context::kGC); |
| } |
| |
| return nullptr; |
| } |
| } // namespace |
| |
| class Evacuator : public Malloced { |
| public: |
| enum EvacuationMode { |
| kObjectsNewToOld, |
| kPageNewToOld, |
| kObjectsOldToOld, |
| kPageNewToNew, |
| }; |
| |
| static const char* EvacuationModeName(EvacuationMode mode) { |
| switch (mode) { |
| case kObjectsNewToOld: |
| return "objects-new-to-old"; |
| case kPageNewToOld: |
| return "page-new-to-old"; |
| case kObjectsOldToOld: |
| return "objects-old-to-old"; |
| case kPageNewToNew: |
| return "page-new-to-new"; |
| } |
| } |
| |
| static inline EvacuationMode ComputeEvacuationMode(MemoryChunk* chunk) { |
| // Note: The order of checks is important in this function. |
| if (chunk->IsFlagSet(MemoryChunk::PAGE_NEW_OLD_PROMOTION)) |
| return kPageNewToOld; |
| if (chunk->IsFlagSet(MemoryChunk::PAGE_NEW_NEW_PROMOTION)) |
| return kPageNewToNew; |
| if (chunk->InYoungGeneration()) return kObjectsNewToOld; |
| return kObjectsOldToOld; |
| } |
| |
| explicit Evacuator(Heap* heap) |
| : heap_(heap), |
| local_pretenuring_feedback_( |
| PretenuringHandler::kInitialFeedbackCapacity), |
| local_allocator_(heap_, |
| CompactionSpaceKind::kCompactionSpaceForMarkCompact), |
| shared_old_allocator_(CreateSharedOldAllocator(heap_)), |
| record_visitor_(heap_, &ephemeron_remembered_set_), |
| new_space_visitor_(heap_, &local_allocator_, |
| shared_old_allocator_.get(), &record_visitor_, |
| &local_pretenuring_feedback_), |
| new_to_new_page_visitor_(heap_, &record_visitor_, |
| &local_pretenuring_feedback_), |
| new_to_old_page_visitor_(heap_, &record_visitor_, |
| &local_pretenuring_feedback_), |
| |
| old_space_visitor_(heap_, &local_allocator_, |
| shared_old_allocator_.get(), &record_visitor_), |
| duration_(0.0), |
| bytes_compacted_(0) {} |
| |
| virtual ~Evacuator() = default; |
| |
| void EvacuatePage(MemoryChunk* chunk); |
| |
| void AddObserver(MigrationObserver* observer) { |
| new_space_visitor_.AddObserver(observer); |
| old_space_visitor_.AddObserver(observer); |
| } |
| |
| // Merge back locally cached info sequentially. Note that this method needs |
| // to be called from the main thread. |
| void Finalize(); |
| |
| protected: |
| // |saved_live_bytes| returns the live bytes of the page that was processed. |
| bool RawEvacuatePage(MemoryChunk* chunk, intptr_t* saved_live_bytes); |
| |
| inline Heap* heap() { return heap_; } |
| |
| void ReportCompactionProgress(double duration, intptr_t bytes_compacted) { |
| duration_ += duration; |
| bytes_compacted_ += bytes_compacted; |
| } |
| |
| Heap* heap_; |
| |
| PretenuringHandler::PretenuringFeedbackMap local_pretenuring_feedback_; |
| |
| // Locally cached collector data. |
| EvacuationAllocator local_allocator_; |
| |
| // Allocator for the shared heap. |
| std::unique_ptr<ConcurrentAllocator> shared_old_allocator_; |
| |
| EphemeronRememberedSet ephemeron_remembered_set_; |
| RecordMigratedSlotVisitor record_visitor_; |
| |
| // Visitors for the corresponding spaces. |
| EvacuateNewSpaceVisitor new_space_visitor_; |
| EvacuateNewSpacePageVisitor<PageEvacuationMode::NEW_TO_NEW> |
| new_to_new_page_visitor_; |
| EvacuateNewSpacePageVisitor<PageEvacuationMode::NEW_TO_OLD> |
| new_to_old_page_visitor_; |
| EvacuateOldSpaceVisitor old_space_visitor_; |
| |
| // Book keeping info. |
| double duration_; |
| intptr_t bytes_compacted_; |
| }; |
| |
| void Evacuator::EvacuatePage(MemoryChunk* chunk) { |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.gc"), "Evacuator::EvacuatePage"); |
| DCHECK(chunk->SweepingDone()); |
| intptr_t saved_live_bytes = 0; |
| double evacuation_time = 0.0; |
| bool success = false; |
| { |
| AlwaysAllocateScope always_allocate(heap()); |
| TimedScope timed_scope(&evacuation_time); |
| success = RawEvacuatePage(chunk, &saved_live_bytes); |
| } |
| ReportCompactionProgress(evacuation_time, saved_live_bytes); |
| if (v8_flags.trace_evacuation) { |
| PrintIsolate(heap()->isolate(), |
| "evacuation[%p]: page=%p new_space=%d " |
| "page_evacuation=%d executable=%d can_promote=%d " |
| "live_bytes=%" V8PRIdPTR " time=%f success=%d\n", |
| static_cast<void*>(this), static_cast<void*>(chunk), |
| chunk->InNewSpace(), |
| chunk->IsFlagSet(Page::PAGE_NEW_OLD_PROMOTION) || |
| chunk->IsFlagSet(Page::PAGE_NEW_NEW_PROMOTION), |
| chunk->IsFlagSet(MemoryChunk::IS_EXECUTABLE), |
| heap()->new_space()->IsPromotionCandidate(chunk), |
| saved_live_bytes, evacuation_time, success); |
| } |
| } |
| |
| void Evacuator::Finalize() { |
| local_allocator_.Finalize(); |
| if (shared_old_allocator_) shared_old_allocator_->FreeLinearAllocationArea(); |
| heap()->tracer()->AddCompactionEvent(duration_, bytes_compacted_); |
| heap()->IncrementPromotedObjectsSize(new_space_visitor_.promoted_size() + |
| new_to_old_page_visitor_.moved_bytes()); |
| heap()->IncrementNewSpaceSurvivingObjectSize( |
| new_space_visitor_.semispace_copied_size() + |
| new_to_new_page_visitor_.moved_bytes()); |
| heap()->IncrementYoungSurvivorsCounter( |
| new_space_visitor_.promoted_size() + |
| new_space_visitor_.semispace_copied_size() + |
| new_to_old_page_visitor_.moved_bytes() + |
| new_to_new_page_visitor_.moved_bytes()); |
| heap()->pretenuring_handler()->MergeAllocationSitePretenuringFeedback( |
| local_pretenuring_feedback_); |
| |
| DCHECK_IMPLIES(v8_flags.minor_mc, ephemeron_remembered_set_.empty()); |
| for (auto it = ephemeron_remembered_set_.begin(); |
| it != ephemeron_remembered_set_.end(); ++it) { |
| auto insert_result = |
| heap()->ephemeron_remembered_set_.insert({it->first, it->second}); |
| if (!insert_result.second) { |
| // Insertion didn't happen, there was already an item. |
| auto set = insert_result.first->second; |
| for (int entry : it->second) { |
| set.insert(entry); |
| } |
| } |
| } |
| } |
| |
| bool Evacuator::RawEvacuatePage(MemoryChunk* chunk, intptr_t* live_bytes) { |
| const EvacuationMode evacuation_mode = ComputeEvacuationMode(chunk); |
| NonAtomicMarkingState* marking_state = heap_->non_atomic_marking_state(); |
| *live_bytes = marking_state->live_bytes(chunk); |
| TRACE_EVENT2(TRACE_DISABLED_BY_DEFAULT("v8.gc"), |
| "FullEvacuator::RawEvacuatePage", "evacuation_mode", |
| EvacuationModeName(evacuation_mode), "live_bytes", *live_bytes); |
| HeapObject failed_object; |
| switch (evacuation_mode) { |
| case kObjectsNewToOld: |
| #if DEBUG |
| new_space_visitor_.DisableAbortEvacuationAtAddress(chunk); |
| #endif // DEBUG |
| LiveObjectVisitor::VisitBlackObjectsNoFail(chunk, marking_state, |
| &new_space_visitor_); |
| marking_state->ClearLiveness(chunk); |
| break; |
| case kPageNewToOld: |
| LiveObjectVisitor::VisitBlackObjectsNoFail(chunk, marking_state, |
| &new_to_old_page_visitor_); |
| new_to_old_page_visitor_.account_moved_bytes( |
| marking_state->live_bytes(chunk)); |
| break; |
| case kPageNewToNew: |
| DCHECK(!v8_flags.minor_mc); |
| LiveObjectVisitor::VisitBlackObjectsNoFail(chunk, marking_state, |
| &new_to_new_page_visitor_); |
| new_to_new_page_visitor_.account_moved_bytes( |
| marking_state->live_bytes(chunk)); |
| break; |
| case kObjectsOldToOld: { |
| RwxMemoryWriteScope rwx_write_scope( |
| "Evacuation of objects in Code space requires write " |
| "access for the " |
| "current worker thread."); |
| #if DEBUG |
| old_space_visitor_.SetUpAbortEvacuationAtAddress(chunk); |
| #endif // DEBUG |
| const bool success = LiveObjectVisitor::VisitBlackObjects( |
| chunk, marking_state, &old_space_visitor_, &failed_object); |
| if (success) { |
| marking_state->ClearLiveness(chunk); |
| } else { |
| if (v8_flags.crash_on_aborted_evacuation) { |
| heap_->FatalProcessOutOfMemory("FullEvacuator::RawEvacuatePage"); |
| } else { |
| // Aborted compaction page. Actual processing happens on the main |
| // thread for simplicity reasons. |
| heap_->mark_compact_collector() |
| ->ReportAbortedEvacuationCandidateDueToOOM( |
| failed_object.address(), static_cast<Page*>(chunk)); |
| return false; |
| } |
| } |
| break; |
| } |
| } |
| |
| return true; |
| } |
| |
| class PageEvacuationJob : public v8::JobTask { |
| public: |
| PageEvacuationJob( |
| Isolate* isolate, std::vector<std::unique_ptr<Evacuator>>* evacuators, |
| std::vector<std::pair<ParallelWorkItem, MemoryChunk*>> evacuation_items) |
| : evacuators_(evacuators), |
| evacuation_items_(std::move(evacuation_items)), |
| remaining_evacuation_items_(evacuation_items_.size()), |
| generator_(evacuation_items_.size()), |
| tracer_(isolate->heap()->tracer()) {} |
| |
| void Run(JobDelegate* delegate) override { |
| RwxMemoryWriteScope::SetDefaultPermissionsForNewThread(); |
| Evacuator* evacuator = (*evacuators_)[delegate->GetTaskId()].get(); |
| if (delegate->IsJoiningThread()) { |
| TRACE_GC(tracer_, GCTracer::Scope::MC_EVACUATE_COPY_PARALLEL); |
| ProcessItems(delegate, evacuator); |
| } else { |
| TRACE_GC_EPOCH(tracer_, GCTracer::Scope::MC_BACKGROUND_EVACUATE_COPY, |
| ThreadKind::kBackground); |
| ProcessItems(delegate, evacuator); |
| } |
| } |
| |
| void ProcessItems(JobDelegate* delegate, Evacuator* evacuator) { |
| while (remaining_evacuation_items_.load(std::memory_order_relaxed) > 0) { |
| base::Optional<size_t> index = generator_.GetNext(); |
| if (!index) return; |
| for (size_t i = *index; i < evacuation_items_.size(); ++i) { |
| auto& work_item = evacuation_items_[i]; |
| if (!work_item.first.TryAcquire()) break; |
| evacuator->EvacuatePage(work_item.second); |
| if (remaining_evacuation_items_.fetch_sub( |
| 1, std::memory_order_relaxed) <= 1) { |
| return; |
| } |
| } |
| } |
| } |
| |
| size_t GetMaxConcurrency(size_t worker_count) const override { |
| const size_t kItemsPerWorker = std::max(1, MB / Page::kPageSize); |
| // Ceiling division to ensure enough workers for all |
| // |remaining_evacuation_items_| |
| const size_t wanted_num_workers = |
| (remaining_evacuation_items_.load(std::memory_order_relaxed) + |
| kItemsPerWorker - 1) / |
| kItemsPerWorker; |
| return std::min<size_t>(wanted_num_workers, evacuators_->size()); |
| } |
| |
| private: |
| std::vector<std::unique_ptr<Evacuator>>* evacuators_; |
| std::vector<std::pair<ParallelWorkItem, MemoryChunk*>> evacuation_items_; |
| std::atomic<size_t> remaining_evacuation_items_{0}; |
| IndexGenerator generator_; |
| |
| GCTracer* tracer_; |
| }; |
| |
| namespace { |
| template <class Evacuator> |
| size_t CreateAndExecuteEvacuationTasks( |
| Heap* heap, |
| std::vector<std::pair<ParallelWorkItem, MemoryChunk*>> evacuation_items) { |
| base::Optional<ProfilingMigrationObserver> profiling_observer; |
| if (heap->isolate()->log_object_relocation()) { |
| profiling_observer.emplace(heap); |
| } |
| std::vector<std::unique_ptr<v8::internal::Evacuator>> evacuators; |
| const int wanted_num_tasks = NumberOfParallelCompactionTasks(heap); |
| for (int i = 0; i < wanted_num_tasks; i++) { |
| auto evacuator = std::make_unique<Evacuator>(heap); |
| if (profiling_observer) { |
| evacuator->AddObserver(&profiling_observer.value()); |
| } |
| evacuators.push_back(std::move(evacuator)); |
| } |
| V8::GetCurrentPlatform() |
| ->CreateJob( |
| v8::TaskPriority::kUserBlocking, |
| std::make_unique<PageEvacuationJob>(heap->isolate(), &evacuators, |
| std::move(evacuation_items))) |
| ->Join(); |
| for (auto& evacuator : evacuators) { |
| evacuator->Finalize(); |
| } |
| return wanted_num_tasks; |
| } |
| |
| // NewSpacePages with more live bytes than this threshold qualify for fast |
| // evacuation. |
| intptr_t NewSpacePageEvacuationThreshold() { |
| return v8_flags.page_promotion_threshold * |
| MemoryChunkLayout::AllocatableMemoryInDataPage() / 100; |
| } |
| |
| bool ShouldMovePage(Page* p, intptr_t live_bytes, intptr_t wasted_bytes, |
| MemoryReductionMode memory_reduction_mode, |
| AlwaysPromoteYoung always_promote_young, |
| PromoteUnusablePages promote_unusable_pages) { |
| Heap* heap = p->heap(); |
| return v8_flags.page_promotion && |
| (memory_reduction_mode == MemoryReductionMode::kNone) && |
| !p->NeverEvacuate() && |
| ((live_bytes + wasted_bytes > NewSpacePageEvacuationThreshold()) || |
| (promote_unusable_pages == PromoteUnusablePages::kYes && |
| !p->WasUsedForAllocation())) && |
| (always_promote_young == AlwaysPromoteYoung::kYes || |
| heap->new_space()->IsPromotionCandidate(p)) && |
| heap->CanExpandOldGeneration(live_bytes); |
| } |
| |
| void TraceEvacuation(Isolate* isolate, size_t pages_count, |
| size_t wanted_num_tasks, size_t live_bytes, |
| size_t aborted_pages) { |
| DCHECK(v8_flags.trace_evacuation); |
| PrintIsolate( |
| isolate, |
| "%8.0f ms: evacuation-summary: parallel=%s pages=%zu " |
| "wanted_tasks=%zu cores=%d live_bytes=%" V8PRIdPTR |
| " compaction_speed=%.f aborted=%zu\n", |
| isolate->time_millis_since_init(), |
| v8_flags.parallel_compaction ? "yes" : "no", pages_count, |
| wanted_num_tasks, V8::GetCurrentPlatform()->NumberOfWorkerThreads() + 1, |
| live_bytes, |
| isolate->heap()->tracer()->CompactionSpeedInBytesPerMillisecond(), |
| aborted_pages); |
| } |
| |
| } // namespace |
| |
| void MarkCompactCollector::EvacuatePagesInParallel() { |
| std::vector<std::pair<ParallelWorkItem, MemoryChunk*>> evacuation_items; |
| intptr_t live_bytes = 0; |
| |
| // Evacuation of new space pages cannot be aborted, so it needs to run |
| // before old space evacuation. |
| bool force_page_promotion = |
| heap()->IsGCWithStack() && !v8_flags.compact_with_stack; |
| for (Page* page : new_space_evacuation_pages_) { |
| intptr_t live_bytes_on_page = non_atomic_marking_state()->live_bytes(page); |
| DCHECK_LT(0, live_bytes_on_page); |
| live_bytes += live_bytes_on_page; |
| MemoryReductionMode memory_reduction_mode = |
| heap()->ShouldReduceMemory() ? MemoryReductionMode::kShouldReduceMemory |
| : MemoryReductionMode::kNone; |
| if (ShouldMovePage(page, live_bytes_on_page, 0, memory_reduction_mode, |
| AlwaysPromoteYoung::kYes, PromoteUnusablePages::kNo) || |
| force_page_promotion) { |
| EvacuateNewSpacePageVisitor<NEW_TO_OLD>::Move(page); |
| page->SetFlag(Page::PAGE_NEW_OLD_PROMOTION); |
| DCHECK_EQ(heap()->old_space(), page->owner()); |
| // The move added page->allocated_bytes to the old space, but we are |
| // going to sweep the page and add page->live_byte_count. |
| heap()->old_space()->DecreaseAllocatedBytes(page->allocated_bytes(), |
| page); |
| } |
| evacuation_items.emplace_back(ParallelWorkItem{}, page); |
| } |
| |
| if (heap()->IsGCWithStack()) { |
| if (!v8_flags.compact_with_stack || |
| !v8_flags.compact_code_space_with_stack) { |
| for (Page* page : old_space_evacuation_pages_) { |
| if (!v8_flags.compact_with_stack || |
| page->owner_identity() == CODE_SPACE) { |
| ReportAbortedEvacuationCandidateDueToFlags(page->area_start(), page); |
| } |
| } |
| } |
| } |
| |
| if (v8_flags.stress_compaction || v8_flags.stress_compaction_random) { |
| // Stress aborting of evacuation by aborting ~10% of evacuation candidates |
| // when stress testing. |
| const double kFraction = 0.05; |
| |
| for (Page* page : old_space_evacuation_pages_) { |
| if (page->IsFlagSet(Page::COMPACTION_WAS_ABORTED)) continue; |
| |
| if (isolate()->fuzzer_rng()->NextDouble() < kFraction) { |
| ReportAbortedEvacuationCandidateDueToFlags(page->area_start(), page); |
| } |
| } |
| } |
| |
| for (Page* page : old_space_evacuation_pages_) { |
| if (page->IsFlagSet(Page::COMPACTION_WAS_ABORTED)) continue; |
| |
| live_bytes += non_atomic_marking_state()->live_bytes(page); |
| evacuation_items.emplace_back(ParallelWorkItem{}, page); |
| } |
| |
| // Promote young generation large objects. |
| if (auto* new_lo_space = heap()->new_lo_space()) { |
| auto* marking_state = heap()->non_atomic_marking_state(); |
| for (auto it = new_lo_space->begin(); it != new_lo_space->end();) { |
| LargePage* current = *(it++); |
| HeapObject object = current->GetObject(); |
| if (marking_state->IsMarked(object)) { |
| heap()->lo_space()->PromoteNewLargeObject(current); |
| current->SetFlag(Page::PAGE_NEW_OLD_PROMOTION); |
| promoted_large_pages_.push_back(current); |
| evacuation_items.emplace_back(ParallelWorkItem{}, current); |
| } |
| } |
| new_lo_space->set_objects_size(0); |
| } |
| |
| const size_t pages_count = evacuation_items.size(); |
| size_t wanted_num_tasks = 0; |
| if (!evacuation_items.empty()) { |
| TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("v8.gc"), |
| "MarkCompactCollector::EvacuatePagesInParallel", "pages", |
| evacuation_items.size()); |
| |
| wanted_num_tasks = CreateAndExecuteEvacuationTasks<Evacuator>( |
| heap(), std::move(evacuation_items)); |
| } |
| |
| const size_t aborted_pages = PostProcessAbortedEvacuationCandidates(); |
| |
| if (v8_flags.trace_evacuation) { |
| TraceEvacuation(isolate(), pages_count, wanted_num_tasks, live_bytes, |
| aborted_pages); |
| } |
| } |
| |
| class EvacuationWeakObjectRetainer : public WeakObjectRetainer { |
| public: |
| Object RetainAs(Object object) override { |
| if (object.IsHeapObject()) { |
| HeapObject heap_object = HeapObject::cast(object); |
| MapWord map_word = heap_object.map_word(kRelaxedLoad); |
| if (map_word.IsForwardingAddress()) { |
| return map_word.ToForwardingAddress(heap_object); |
| } |
| } |
| return object; |
| } |
| }; |
| |
| template <class Visitor, typename MarkingState> |
| bool LiveObjectVisitor::VisitBlackObjects(MemoryChunk* chunk, |
| MarkingState* marking_state, |
| Visitor* visitor, |
| HeapObject* failed_object) { |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.gc"), |
| "LiveObjectVisitor::VisitBlackObjects"); |
| for (auto [object, size] : LiveObjectRange(Page::cast(chunk))) { |
| if (!visitor->Visit(object, size)) { |
| *failed_object = object; |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| template <class Visitor, typename MarkingState> |
| void LiveObjectVisitor::VisitBlackObjectsNoFail(MemoryChunk* chunk, |
| MarkingState* marking_state, |
| Visitor* visitor) { |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.gc"), |
| "LiveObjectVisitor::VisitBlackObjectsNoFail"); |
| if (chunk->IsLargePage()) { |
| HeapObject object = reinterpret_cast<LargePage*>(chunk)->GetObject(); |
| if (marking_state->IsMarked(object)) { |
| const bool success = visitor->Visit(object, object.Size()); |
| USE(success); |
| DCHECK(success); |
| } |
| } else { |
| for (auto [object, size] : LiveObjectRange(Page::cast(chunk))) { |
| DCHECK(marking_state->IsMarked(object)); |
| const bool success = visitor->Visit(object, size); |
| USE(success); |
| DCHECK(success); |
| } |
| } |
| } |
| |
| template <typename MarkingState> |
| void LiveObjectVisitor::RecomputeLiveBytes(MemoryChunk* chunk, |
| MarkingState* marking_state) { |
| int new_live_size = 0; |
| for (auto object_and_size : LiveObjectRange(Page::cast(chunk))) { |
| new_live_size += ALIGN_TO_ALLOCATION_ALIGNMENT(object_and_size.second); |
| } |
| marking_state->SetLiveBytes(chunk, new_live_size); |
| } |
| |
| void MarkCompactCollector::Evacuate() { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_EVACUATE); |
| base::MutexGuard guard(heap()->relocation_mutex()); |
| |
| { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_EVACUATE_PROLOGUE); |
| EvacuatePrologue(); |
| } |
| |
| { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_EVACUATE_COPY); |
| EvacuatePagesInParallel(); |
| } |
| |
| UpdatePointersAfterEvacuation(); |
| |
| { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_EVACUATE_CLEAN_UP); |
| |
| for (Page* p : new_space_evacuation_pages_) { |
| // Full GCs don't promote pages within new space. |
| DCHECK(!p->IsFlagSet(Page::PAGE_NEW_NEW_PROMOTION)); |
| if (p->IsFlagSet(Page::PAGE_NEW_OLD_PROMOTION)) { |
| p->ClearFlag(Page::PAGE_NEW_OLD_PROMOTION); |
| DCHECK_EQ(OLD_SPACE, p->owner_identity()); |
| sweeper()->AddPage(OLD_SPACE, p, Sweeper::REGULAR); |
| } else if (v8_flags.minor_mc) { |
| // Sweep non-promoted pages to add them back to the free list. |
| DCHECK_EQ(NEW_SPACE, p->owner_identity()); |
| DCHECK_EQ(0, non_atomic_marking_state()->live_bytes(p)); |
| DCHECK(p->SweepingDone()); |
| PagedNewSpace* space = heap()->paged_new_space(); |
| if (space->ShouldReleaseEmptyPage()) { |
| space->ReleasePage(p); |
| } else { |
| sweeper()->SweepEmptyNewSpacePage(p); |
| } |
| } |
| } |
| new_space_evacuation_pages_.clear(); |
| |
| for (LargePage* p : promoted_large_pages_) { |
| DCHECK(p->IsFlagSet(Page::PAGE_NEW_OLD_PROMOTION)); |
| p->ClearFlag(Page::PAGE_NEW_OLD_PROMOTION); |
| HeapObject object = p->GetObject(); |
| non_atomic_marking_state()->MarkBitFrom(object).Clear(); |
| p->ProgressBar().ResetIfEnabled(); |
| non_atomic_marking_state()->SetLiveBytes(p, 0); |
| } |
| promoted_large_pages_.clear(); |
| |
| for (Page* p : old_space_evacuation_pages_) { |
| if (p->IsFlagSet(Page::COMPACTION_WAS_ABORTED)) { |
| sweeper()->AddPage(p->owner_identity(), p, Sweeper::REGULAR); |
| p->ClearFlag(Page::COMPACTION_WAS_ABORTED); |
| } |
| } |
| } |
| |
| { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_EVACUATE_EPILOGUE); |
| EvacuateEpilogue(); |
| } |
| |
| #ifdef VERIFY_HEAP |
| if (v8_flags.verify_heap && !sweeper()->sweeping_in_progress()) { |
| FullEvacuationVerifier verifier(heap()); |
| verifier.Run(); |
| } |
| #endif // VERIFY_HEAP |
| } |
| |
| class UpdatingItem : public ParallelWorkItem { |
| public: |
| virtual ~UpdatingItem() = default; |
| virtual void Process() = 0; |
| }; |
| |
| class PointersUpdatingJob : public v8::JobTask { |
| public: |
| explicit PointersUpdatingJob( |
| Isolate* isolate, |
| std::vector<std::unique_ptr<UpdatingItem>> updating_items) |
| : updating_items_(std::move(updating_items)), |
| remaining_updating_items_(updating_items_.size()), |
| generator_(updating_items_.size()), |
| tracer_(isolate->heap()->tracer()) {} |
| |
| void Run(JobDelegate* delegate) override { |
| RwxMemoryWriteScope::SetDefaultPermissionsForNewThread(); |
| if (delegate->IsJoiningThread()) { |
| TRACE_GC(tracer_, GCTracer::Scope::MC_EVACUATE_UPDATE_POINTERS_PARALLEL); |
| UpdatePointers(delegate); |
| } else { |
| TRACE_GC_EPOCH(tracer_, |
| GCTracer::Scope::MC_BACKGROUND_EVACUATE_UPDATE_POINTERS, |
| ThreadKind::kBackground); |
| UpdatePointers(delegate); |
| } |
| } |
| |
| void UpdatePointers(JobDelegate* delegate) { |
| while (remaining_updating_items_.load(std::memory_order_relaxed) > 0) { |
| base::Optional<size_t> index = generator_.GetNext(); |
| if (!index) return; |
| for (size_t i = *index; i < updating_items_.size(); ++i) { |
| auto& work_item = updating_items_[i]; |
| if (!work_item->TryAcquire()) break; |
| work_item->Process(); |
| if (remaining_updating_items_.fetch_sub(1, std::memory_order_relaxed) <= |
| 1) { |
| return; |
| } |
| } |
| } |
| } |
| |
| size_t GetMaxConcurrency(size_t worker_count) const override { |
| size_t items = remaining_updating_items_.load(std::memory_order_relaxed); |
| if (!v8_flags.parallel_pointer_update) return items > 0; |
| const size_t kMaxPointerUpdateTasks = 8; |
| size_t max_concurrency = std::min<size_t>(kMaxPointerUpdateTasks, items); |
| DCHECK_IMPLIES(items > 0, max_concurrency > 0); |
| return max_concurrency; |
| } |
| |
| private: |
| std::vector<std::unique_ptr<UpdatingItem>> updating_items_; |
| std::atomic<size_t> remaining_updating_items_{0}; |
| IndexGenerator generator_; |
| |
| GCTracer* tracer_; |
| }; |
| |
| template <typename MarkingState> |
| class ToSpaceUpdatingItem : public UpdatingItem { |
| public: |
| explicit ToSpaceUpdatingItem(Heap* heap, MemoryChunk* chunk, Address start, |
| Address end, MarkingState* marking_state) |
| : heap_(heap), |
| chunk_(chunk), |
| start_(start), |
| end_(end), |
| marking_state_(marking_state) {} |
| ~ToSpaceUpdatingItem() override = default; |
| |
| void Process() override { |
| if (chunk_->IsFlagSet(Page::PAGE_NEW_NEW_PROMOTION)) { |
| // New->new promoted pages contain garbage so they require iteration using |
| // markbits. |
| ProcessVisitLive(); |
| } else { |
| ProcessVisitAll(); |
| } |
| } |
| |
| private: |
| void ProcessVisitAll() { |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.gc"), |
| "ToSpaceUpdatingItem::ProcessVisitAll"); |
| PointersUpdatingVisitor visitor(heap_); |
| for (Address cur = start_; cur < end_;) { |
| HeapObject object = HeapObject::FromAddress(cur); |
| Map map = object.map(visitor.cage_base()); |
| int size = object.SizeFromMap(map); |
| object.IterateBodyFast(map, size, &visitor); |
| cur += size; |
| } |
| } |
| |
| void ProcessVisitLive() { |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.gc"), |
| "ToSpaceUpdatingItem::ProcessVisitLive"); |
| // For young generation evacuations we want to visit grey objects, for |
| // full MC, we need to visit black objects. |
| PointersUpdatingVisitor visitor(heap_); |
| for (auto object_and_size : LiveObjectRange(Page::cast(chunk_))) { |
| object_and_size.first.IterateBodyFast(visitor.cage_base(), &visitor); |
| } |
| } |
| |
| Heap* heap_; |
| MemoryChunk* chunk_; |
| Address start_; |
| Address end_; |
| MarkingState* marking_state_; |
| }; |
| |
| namespace { |
| |
| class RememberedSetUpdatingItem : public UpdatingItem { |
| public: |
| explicit RememberedSetUpdatingItem(Heap* heap, MemoryChunk* chunk) |
| : heap_(heap), |
| marking_state_(heap_->non_atomic_marking_state()), |
| chunk_(chunk), |
| record_old_to_shared_slots_(heap->isolate()->has_shared_space() && |
| !chunk->InWritableSharedSpace()) {} |
| ~RememberedSetUpdatingItem() override = default; |
| |
| void Process() override { |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.gc"), |
| "RememberedSetUpdatingItem::Process"); |
| CodePageMemoryModificationScope memory_modification_scope(chunk_); |
| UpdateUntypedPointers(); |
| UpdateTypedPointers(); |
| } |
| |
| private: |
| template <typename TSlot> |
| inline void CheckSlotForOldToSharedUntyped(PtrComprCageBase cage_base, |
| MemoryChunk* chunk, TSlot slot) { |
| HeapObject heap_object; |
| |
| if (!slot.load(cage_base).GetHeapObject(&heap_object)) { |
| return; |
| } |
| |
| if (heap_object.InWritableSharedSpace()) { |
| RememberedSet<OLD_TO_SHARED>::Insert<AccessMode::NON_ATOMIC>( |
| chunk, slot.address()); |
| } |
| } |
| |
| inline void CheckSlotForOldToSharedTyped(MemoryChunk* chunk, |
| SlotType slot_type, Address addr) { |
| HeapObject heap_object = |
| UpdateTypedSlotHelper::GetTargetObject(chunk->heap(), slot_type, addr); |
| |
| #if DEBUG |
| UpdateTypedSlotHelper::UpdateTypedSlot( |
| chunk->heap(), slot_type, addr, |
| [heap_object](FullMaybeObjectSlot slot) { |
| DCHECK_EQ((*slot).GetHeapObjectAssumeStrong(), heap_object); |
| return KEEP_SLOT; |
| }); |
| #endif // DEBUG |
| |
| if (heap_object.InWritableSharedSpace()) { |
| const uintptr_t offset = addr - chunk->address(); |
| DCHECK_LT(offset, static_cast<uintptr_t>(TypedSlotSet::kMaxOffset)); |
| RememberedSet<OLD_TO_SHARED>::InsertTyped(chunk, slot_type, |
| static_cast<uint32_t>(offset)); |
| } |
| } |
| |
| template <typename TSlot> |
| inline void CheckAndUpdateOldToNewSlot(TSlot slot) { |
| static_assert( |
| std::is_same<TSlot, FullMaybeObjectSlot>::value || |
| std::is_same<TSlot, MaybeObjectSlot>::value, |
| "Only FullMaybeObjectSlot and MaybeObjectSlot are expected here"); |
| HeapObject heap_object; |
| if (!(*slot).GetHeapObject(&heap_object)) return; |
| if (!Heap::InYoungGeneration(heap_object)) return; |
| |
| if (v8_flags.minor_mc && !Heap::IsLargeObject(heap_object)) { |
| DCHECK(Heap::InToPage(heap_object)); |
| } else { |
| DCHECK(Heap::InFromPage(heap_object)); |
| } |
| |
| MapWord map_word = heap_object.map_word(kRelaxedLoad); |
| if (map_word.IsForwardingAddress()) { |
| using THeapObjectSlot = typename TSlot::THeapObjectSlot; |
| HeapObjectReference::Update(THeapObjectSlot(slot), |
| map_word.ToForwardingAddress(heap_object)); |
| } else { |
| // OLD_TO_NEW slots are recorded in dead memory, so they might point to |
| // dead objects. |
| DCHECK(!marking_state_->IsMarked(heap_object)); |
| } |
| } |
| |
| void UpdateUntypedPointers() { |
| UpdateUntypedOldToNewPointers(); |
| UpdateUntypedOldToOldPointers(); |
| UpdateUntypedOldToCodePointers(); |
| UpdateUntypedOldToSharedPointers(); |
| } |
| |
| void UpdateUntypedOldToNewPointers() { |
| if (chunk_->slot_set<OLD_TO_NEW, AccessMode::NON_ATOMIC>()) { |
| const PtrComprCageBase cage_base = heap_->isolate(); |
| // Marking bits are cleared already when the page is already swept. This |
| // is fine since in that case the sweeper has already removed dead invalid |
| // objects as well. |
| InvalidatedSlotsFilter::LivenessCheck liveness_check = |
| !chunk_->SweepingDone() ? InvalidatedSlotsFilter::LivenessCheck::kYes |
| : InvalidatedSlotsFilter::LivenessCheck::kNo; |
| InvalidatedSlotsFilter filter = |
| InvalidatedSlotsFilter::OldToNew(chunk_, liveness_check); |
| RememberedSet<OLD_TO_NEW>::Iterate( |
| chunk_, |
| [this, &filter, cage_base](MaybeObjectSlot slot) { |
| if (!filter.IsValid(slot.address())) return REMOVE_SLOT; |
| CheckAndUpdateOldToNewSlot(slot); |
| // A new space string might have been promoted into the shared heap |
| // during GC. |
| if (record_old_to_shared_slots_) { |
| CheckSlotForOldToSharedUntyped(cage_base, chunk_, slot); |
| } |
| // Always keep slot since all slots are dropped at once after |
| // iteration. |
| return KEEP_SLOT; |
| }, |
| SlotSet::KEEP_EMPTY_BUCKETS); |
| } |
| |
| // The invalidated slots are not needed after old-to-new slots were |
| // processed. |
| chunk_->ReleaseInvalidatedSlots<OLD_TO_NEW>(); |
| // Full GCs will empty new space, so OLD_TO_NEW is empty. |
| chunk_->ReleaseSlotSet<OLD_TO_NEW>(); |
| } |
| |
| void UpdateUntypedOldToOldPointers() { |
| if (chunk_->slot_set<OLD_TO_OLD, AccessMode::NON_ATOMIC>()) { |
| const PtrComprCageBase cage_base = heap_->isolate(); |
| InvalidatedSlotsFilter filter = InvalidatedSlotsFilter::OldToOld( |
| chunk_, InvalidatedSlotsFilter::LivenessCheck::kNo); |
| RememberedSet<OLD_TO_OLD>::Iterate( |
| chunk_, |
| [this, &filter, cage_base](MaybeObjectSlot slot) { |
| if (filter.IsValid(slot.address())) { |
| UpdateSlot<AccessMode::NON_ATOMIC>(cage_base, slot); |
| // A string might have been promoted into the shared heap during |
| // GC. |
| if (record_old_to_shared_slots_) { |
| CheckSlotForOldToSharedUntyped(cage_base, chunk_, slot); |
| } |
| } |
| // Always keep slot since all slots are dropped at once after |
| // iteration. |
| return KEEP_SLOT; |
| }, |
| SlotSet::KEEP_EMPTY_BUCKETS); |
| chunk_->ReleaseSlotSet<OLD_TO_OLD>(); |
| } |
| |
| // The invalidated slots are not needed after old-to-old slots were |
| // processed. |
| chunk_->ReleaseInvalidatedSlots<OLD_TO_OLD>(); |
| } |
| |
| void UpdateUntypedOldToCodePointers() { |
| if (chunk_->slot_set<OLD_TO_CODE, AccessMode::NON_ATOMIC>()) { |
| const PtrComprCageBase cage_base = heap_->isolate(); |
| #ifdef V8_EXTERNAL_CODE_SPACE |
| const PtrComprCageBase code_cage_base(heap_->isolate()->code_cage_base()); |
| #else |
| const PtrComprCageBase code_cage_base = cage_base; |
| #endif |
| RememberedSet<OLD_TO_CODE>::Iterate( |
| chunk_, |
| [=](MaybeObjectSlot slot) { |
| HeapObject host = HeapObject::FromAddress( |
| slot.address() - Code::kInstructionStreamOffset); |
| DCHECK(host.IsCode(cage_base)); |
| UpdateStrongCodeSlot<AccessMode::NON_ATOMIC>( |
| host, cage_base, code_cage_base, |
| CodeObjectSlot(slot.address())); |
| // Always keep slot since all slots are dropped at once after |
| // iteration. |
| return KEEP_SLOT; |
| }, |
| SlotSet::FREE_EMPTY_BUCKETS); |
| chunk_->ReleaseSlotSet<OLD_TO_CODE>(); |
| } |
| |
| // The invalidated slots are not needed after old-to-code slots were |
| // processed, but since there are no invalidated OLD_TO_CODE slots, |
| // there's nothing to clear. |
| DCHECK_NULL(chunk_->invalidated_slots<OLD_TO_CODE>()); |
| } |
| |
| void UpdateUntypedOldToSharedPointers() { |
| if (chunk_->slot_set<OLD_TO_SHARED, AccessMode::NON_ATOMIC>()) { |
| // Client GCs need to remove invalidated OLD_TO_SHARED slots. |
| InvalidatedSlotsFilter filter = InvalidatedSlotsFilter::OldToShared( |
| chunk_, InvalidatedSlotsFilter::LivenessCheck::kNo); |
| RememberedSet<OLD_TO_SHARED>::Iterate( |
| chunk_, |
| [&filter](MaybeObjectSlot slot) { |
| return filter.IsValid(slot.address()) ? KEEP_SLOT : REMOVE_SLOT; |
| }, |
| SlotSet::FREE_EMPTY_BUCKETS); |
| } |
| |
| // The invalidated slots are not needed after old-to-shared slots were |
| // processed. |
| chunk_->ReleaseInvalidatedSlots<OLD_TO_SHARED>(); |
| } |
| |
| void UpdateTypedPointers() { |
| UpdateTypedOldToNewPointers(); |
| UpdateTypedOldToOldPointers(); |
| } |
| |
| void UpdateTypedOldToNewPointers() { |
| if (chunk_->typed_slot_set<OLD_TO_NEW, AccessMode::NON_ATOMIC>() == nullptr) |
| return; |
| const auto check_and_update_old_to_new_slot_fn = |
| [this](FullMaybeObjectSlot slot) { |
| CheckAndUpdateOldToNewSlot(slot); |
| return KEEP_SLOT; |
| }; |
| RememberedSet<OLD_TO_NEW>::IterateTyped( |
| chunk_, [this, &check_and_update_old_to_new_slot_fn](SlotType slot_type, |
| Address slot) { |
| UpdateTypedSlotHelper::UpdateTypedSlot( |
| heap_, slot_type, slot, check_and_update_old_to_new_slot_fn); |
| // A new space string might have been promoted into the shared heap |
| // during GC. |
| if (record_old_to_shared_slots_) { |
| CheckSlotForOldToSharedTyped(chunk_, slot_type, slot); |
| } |
| // Always keep slot since all slots are dropped at once after |
| // iteration. |
| return KEEP_SLOT; |
| }); |
| // Full GCs will empty new space, so OLD_TO_NEW is empty. |
| chunk_->ReleaseTypedSlotSet<OLD_TO_NEW>(); |
| } |
| |
| void UpdateTypedOldToOldPointers() { |
| if (chunk_->typed_slot_set<OLD_TO_OLD, AccessMode::NON_ATOMIC>() == nullptr) |
| return; |
| RememberedSet<OLD_TO_OLD>::IterateTyped( |
| chunk_, [this](SlotType slot_type, Address slot) { |
| // Using UpdateStrongSlot is OK here, because there are no weak |
| // typed slots. |
| PtrComprCageBase cage_base = heap_->isolate(); |
| SlotCallbackResult result = UpdateTypedSlotHelper::UpdateTypedSlot( |
| heap_, slot_type, slot, [cage_base](FullMaybeObjectSlot slot) { |
| UpdateStrongSlot<AccessMode::NON_ATOMIC>(cage_base, slot); |
| // Always keep slot since all slots are dropped at once after |
| // iteration. |
| return KEEP_SLOT; |
| }); |
| // A string might have been promoted into the shared heap during GC. |
| if (record_old_to_shared_slots_) { |
| CheckSlotForOldToSharedTyped(chunk_, slot_type, slot); |
| } |
| return result; |
| }); |
| chunk_->ReleaseTypedSlotSet<OLD_TO_OLD>(); |
| } |
| |
| Heap* heap_; |
| NonAtomicMarkingState* marking_state_; |
| MemoryChunk* chunk_; |
| const bool record_old_to_shared_slots_; |
| }; |
| |
| } // namespace |
| |
| namespace { |
| template <typename IterateableSpace> |
| void CollectRememberedSetUpdatingItems( |
| std::vector<std::unique_ptr<UpdatingItem>>* items, |
| IterateableSpace* space) { |
| for (MemoryChunk* chunk : *space) { |
| // No need to update pointers on evacuation candidates. Evacuated pages will |
| // be released after this phase. |
| if (chunk->IsEvacuationCandidate()) continue; |
| if (chunk->HasRecordedSlots()) { |
| items->emplace_back( |
| std::make_unique<RememberedSetUpdatingItem>(space->heap(), chunk)); |
| } |
| } |
| } |
| } // namespace |
| |
| class EphemeronTableUpdatingItem : public UpdatingItem { |
| public: |
| enum EvacuationState { kRegular, kAborted }; |
| |
| explicit EphemeronTableUpdatingItem(Heap* heap) : heap_(heap) {} |
| ~EphemeronTableUpdatingItem() override = default; |
| |
| void Process() override { |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.gc"), |
| "EphemeronTableUpdatingItem::Process"); |
| PtrComprCageBase cage_base(heap_->isolate()); |
| |
| for (auto it = heap_->ephemeron_remembered_set_.begin(); |
| it != heap_->ephemeron_remembered_set_.end();) { |
| EphemeronHashTable table = it->first; |
| auto& indices = it->second; |
| if (table.map_word(cage_base, kRelaxedLoad).IsForwardingAddress()) { |
| // The table has moved, and RecordMigratedSlotVisitor::VisitEphemeron |
| // inserts entries for the moved table into ephemeron_remembered_set_. |
| it = heap_->ephemeron_remembered_set_.erase(it); |
| continue; |
| } |
| DCHECK(table.map(cage_base).IsMap(cage_base)); |
| DCHECK(table.IsEphemeronHashTable(cage_base)); |
| for (auto iti = indices.begin(); iti != indices.end();) { |
| // EphemeronHashTable keys must be heap objects. |
| HeapObjectSlot key_slot(table.RawFieldOfElementAt( |
| EphemeronHashTable::EntryToIndex(InternalIndex(*iti)))); |
| HeapObject key = key_slot.ToHeapObject(); |
| MapWord map_word = key.map_word(cage_base, kRelaxedLoad); |
| if (map_word.IsForwardingAddress()) { |
| key = map_word.ToForwardingAddress(key); |
| key_slot.StoreHeapObject(key); |
| } |
| if (!heap_->InYoungGeneration(key)) { |
| iti = indices.erase(iti); |
| } else { |
| ++iti; |
| } |
| } |
| if (indices.size() == 0) { |
| it = heap_->ephemeron_remembered_set_.erase(it); |
| } else { |
| ++it; |
| } |
| } |
| } |
| |
| private: |
| Heap* const heap_; |
| }; |
| |
| void MarkCompactCollector::UpdatePointersAfterEvacuation() { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MC_EVACUATE_UPDATE_POINTERS); |
| |
| { |
| TRACE_GC(heap()->tracer(), |
| GCTracer::Scope::MC_EVACUATE_UPDATE_POINTERS_TO_NEW_ROOTS); |
| // The external string table is updated at the end. |
| PointersUpdatingVisitor updating_visitor(heap()); |
| heap_->IterateRootsIncludingClients( |
| &updating_visitor, |
| base::EnumSet<SkipRoot>{SkipRoot::kExternalStringTable, |
| SkipRoot::kConservativeStack, |
| SkipRoot::kReadOnlyBuiltins}); |
| } |
| |
| { |
| TRACE_GC(heap()->tracer(), |
| GCTracer::Scope::MC_EVACUATE_UPDATE_POINTERS_CLIENT_HEAPS); |
| UpdatePointersInClientHeaps(); |
| } |
| |
| { |
| TRACE_GC(heap()->tracer(), |
| GCTracer::Scope::MC_EVACUATE_UPDATE_POINTERS_SLOTS_MAIN); |
| std::vector<std::unique_ptr<UpdatingItem>> updating_items; |
| |
| CollectRememberedSetUpdatingItems(&updating_items, heap()->old_space()); |
| CollectRememberedSetUpdatingItems(&updating_items, heap()->code_space()); |
| if (heap()->shared_space()) { |
| CollectRememberedSetUpdatingItems(&updating_items, |
| heap()->shared_space()); |
| } |
| CollectRememberedSetUpdatingItems(&updating_items, heap()->lo_space()); |
| CollectRememberedSetUpdatingItems(&updating_items, heap()->code_lo_space()); |
| if (heap()->shared_lo_space()) { |
| CollectRememberedSetUpdatingItems(&updating_items, |
| heap()->shared_lo_space()); |
| } |
| |
| // Iterating to space may require a valid body descriptor for e.g. |
| // WasmStruct which races with updating a slot in Map. Since to space is |
| // empty after a full GC, such races can't happen. |
| DCHECK_IMPLIES(heap()->new_space(), heap()->new_space()->Size() == 0); |
| |
| updating_items.push_back( |
| std::make_unique<EphemeronTableUpdatingItem>(heap())); |
| |
| V8::GetCurrentPlatform() |
| ->CreateJob(v8::TaskPriority::kUserBlocking, |
| std::make_unique<PointersUpdatingJob>( |
| isolate(), std::move(updating_items))) |
| ->Join(); |
| } |
| |
| { |
| TRACE_GC(heap()->tracer(), |
| GCTracer::Scope::MC_EVACUATE_UPDATE_POINTERS_WEAK); |
| // Update pointers from external string table. |
| heap_->UpdateReferencesInExternalStringTable( |
| &UpdateReferenceInExternalStringTableEntry); |
| |
| // Update pointers in string forwarding table. |
| // When GC was performed without a stack, the table was cleared and this |
| // does nothing. In the case this was a GC with stack, we need to update |
| // the entries for evacuated objects. |
| isolate()->string_forwarding_table()->UpdateAfterFullEvacuation(); |
| |
| EvacuationWeakObjectRetainer evacuation_object_retainer; |
| heap()->ProcessWeakListRoots(&evacuation_object_retainer); |
| } |
| |
| // Flush the inner_pointer_to_code_cache which may now have stale contents. |
| isolate()->inner_pointer_to_code_cache()->Flush(); |
| } |
| |
| void MarkCompactCollector::UpdatePointersInClientHeaps() { |
| if (!isolate()->is_shared_space_isolate()) return; |
| |
| isolate()->global_safepoint()->IterateClientIsolates( |
| [this](Isolate* client) { UpdatePointersInClientHeap(client); }); |
| } |
| |
| void MarkCompactCollector::UpdatePointersInClientHeap(Isolate* client) { |
| PtrComprCageBase cage_base(client); |
| MemoryChunkIterator chunk_iterator(client->heap()); |
| |
| while (chunk_iterator.HasNext()) { |
| MemoryChunk* chunk = chunk_iterator.Next(); |
| CodePageMemoryModificationScope unprotect_code_page(chunk); |
| |
| DCHECK_NULL(chunk->invalidated_slots<OLD_TO_SHARED>()); |
| RememberedSet<OLD_TO_SHARED>::Iterate( |
| chunk, |
| [cage_base](MaybeObjectSlot slot) { |
| return UpdateOldToSharedSlot(cage_base, slot); |
| }, |
| SlotSet::FREE_EMPTY_BUCKETS); |
| |
| if (chunk->InYoungGeneration()) chunk->ReleaseSlotSet<OLD_TO_SHARED>(); |
| |
| RememberedSet<OLD_TO_SHARED>::IterateTyped( |
| chunk, [this](SlotType slot_type, Address slot) { |
| // Using UpdateStrongSlot is OK here, because there are no weak |
| // typed slots. |
| PtrComprCageBase cage_base = heap_->isolate(); |
| return UpdateTypedSlotHelper::UpdateTypedSlot( |
| heap_, slot_type, slot, [cage_base](FullMaybeObjectSlot slot) { |
| return UpdateStrongOldToSharedSlot(cage_base, slot); |
| }); |
| }); |
| if (chunk->InYoungGeneration()) chunk->ReleaseTypedSlotSet<OLD_TO_SHARED>(); |
| } |
| } |
| |
| void MarkCompactCollector::ReportAbortedEvacuationCandidateDueToOOM( |
| Address failed_start, Page* page) { |
| base::MutexGuard guard(&mutex_); |
| aborted_evacuation_candidates_due_to_oom_.push_back( |
| std::make_pair(failed_start, page)); |
| } |
| |
| void MarkCompactCollector::ReportAbortedEvacuationCandidateDueToFlags( |
| Address failed_start, Page* page) { |
| DCHECK(!page->IsFlagSet(Page::COMPACTION_WAS_ABORTED)); |
| page->SetFlag(Page::COMPACTION_WAS_ABORTED); |
| base::MutexGuard guard(&mutex_); |
| aborted_evacuation_candidates_due_to_flags_.push_back( |
| std::make_pair(failed_start, page)); |
| } |
| |
| namespace { |
| |
| void ReRecordPage(Heap* heap, Address failed_start, Page* page) { |
| DCHECK(page->IsFlagSet(Page::COMPACTION_WAS_ABORTED)); |
| |
| NonAtomicMarkingState* marking_state = heap->non_atomic_marking_state(); |
| // Aborted compaction page. We have to record slots here, since we |
| // might not have recorded them in first place. |
| |
| // Remove mark bits in evacuated area. |
| marking_state->bitmap(page)->ClearRange<AccessMode::NON_ATOMIC>( |
| MarkingBitmap::AddressToIndex(page->area_start()), |
| MarkingBitmap::LimitAddressToIndex(failed_start)); |
| |
| // Remove outdated slots. |
| RememberedSet<OLD_TO_NEW>::RemoveRange(page, page->address(), failed_start, |
| SlotSet::FREE_EMPTY_BUCKETS); |
| RememberedSet<OLD_TO_NEW>::RemoveRangeTyped(page, page->address(), |
| failed_start); |
| |
| RememberedSet<OLD_TO_SHARED>::RemoveRange(page, page->address(), failed_start, |
| SlotSet::FREE_EMPTY_BUCKETS); |
| RememberedSet<OLD_TO_SHARED>::RemoveRangeTyped(page, page->address(), |
| failed_start); |
| |
| // Remove invalidated slots. |
| if (failed_start > page->area_start()) { |
| InvalidatedSlotsCleanup old_to_new_cleanup = |
| InvalidatedSlotsCleanup::OldToNew(page); |
| old_to_new_cleanup.Free(page->area_start(), failed_start); |
| |
| InvalidatedSlotsCleanup old_to_shared_cleanup = |
| InvalidatedSlotsCleanup::OldToShared(page); |
| old_to_shared_cleanup.Free(page->area_start(), failed_start); |
| } |
| |
| // Recompute live bytes. |
| LiveObjectVisitor::RecomputeLiveBytes(page, marking_state); |
| // Re-record slots. |
| EvacuateRecordOnlyVisitor record_visitor(heap); |
| LiveObjectVisitor::VisitBlackObjectsNoFail(page, marking_state, |
| &record_visitor); |
| // Array buffers will be processed during pointer updating. |
| } |
| |
| } // namespace |
| |
| size_t MarkCompactCollector::PostProcessAbortedEvacuationCandidates() { |
| CHECK_IMPLIES(v8_flags.crash_on_aborted_evacuation, |
| aborted_evacuation_candidates_due_to_oom_.empty()); |
| for (auto start_and_page : aborted_evacuation_candidates_due_to_oom_) { |
| Page* page = start_and_page.second; |
| DCHECK(!page->IsFlagSet(Page::COMPACTION_WAS_ABORTED)); |
| page->SetFlag(Page::COMPACTION_WAS_ABORTED); |
| } |
| for (auto start_and_page : aborted_evacuation_candidates_due_to_oom_) { |
| ReRecordPage(heap(), start_and_page.first, start_and_page.second); |
| } |
| for (auto start_and_page : aborted_evacuation_candidates_due_to_flags_) { |
| ReRecordPage(heap(), start_and_page.first, start_and_page.second); |
| } |
| const size_t aborted_pages = |
| aborted_evacuation_candidates_due_to_oom_.size() + |
| aborted_evacuation_candidates_due_to_flags_.size(); |
| size_t aborted_pages_verified = 0; |
| for (Page* p : old_space_evacuation_pages_) { |
| if (p->IsFlagSet(Page::COMPACTION_WAS_ABORTED)) { |
| // Only clear EVACUATION_CANDIDATE flag after all slots were re-recorded |
| // on all aborted pages. Necessary since repopulating |
| // OLD_TO_OLD still requires the EVACUATION_CANDIDATE flag. After clearing |
| // the evacuation candidate flag the page is again in a regular state. |
| p->ClearEvacuationCandidate(); |
| aborted_pages_verified++; |
| } else { |
| DCHECK(p->IsEvacuationCandidate()); |
| DCHECK(p->SweepingDone()); |
| } |
| } |
| DCHECK_EQ(aborted_pages_verified, aborted_pages); |
| USE(aborted_pages_verified); |
| return aborted_pages; |
| } |
| |
| void MarkCompactCollector::ReleaseEvacuationCandidates() { |
| for (Page* p : old_space_evacuation_pages_) { |
| if (!p->IsEvacuationCandidate()) continue; |
| PagedSpace* space = static_cast<PagedSpace*>(p->owner()); |
| non_atomic_marking_state()->SetLiveBytes(p, 0); |
| CHECK(p->SweepingDone()); |
| space->ReleasePage(p); |
| } |
| old_space_evacuation_pages_.clear(); |
| compacting_ = false; |
| } |
| |
| void MarkCompactCollector::StartSweepNewSpace() { |
| PagedSpaceForNewSpace* paged_space = heap()->paged_new_space()->paged_space(); |
| paged_space->ClearAllocatorState(); |
| |
| int will_be_swept = 0; |
| |
| DCHECK_EQ(Heap::ResizeNewSpaceMode::kNone, resize_new_space_); |
| resize_new_space_ = heap()->ShouldResizeNewSpace(); |
| if (resize_new_space_ == Heap::ResizeNewSpaceMode::kShrink) { |
| paged_space->StartShrinking(); |
| } |
| |
| DCHECK(empty_new_space_pages_to_be_swept_.empty()); |
| for (auto it = paged_space->begin(); it != paged_space->end();) { |
| Page* p = *(it++); |
| DCHECK(p->SweepingDone()); |
| |
| if (non_atomic_marking_state()->live_bytes(p) > 0) { |
| // Non-empty pages will be evacuated/promoted. |
| continue; |
| } |
| |
| if (paged_space->ShouldReleaseEmptyPage()) { |
| paged_space->ReleasePage(p); |
| } else { |
| empty_new_space_pages_to_be_swept_.push_back(p); |
| } |
| will_be_swept++; |
| } |
| |
| if (v8_flags.gc_verbose) { |
| PrintIsolate(isolate(), "sweeping: space=%s initialized_for_sweeping=%d", |
| paged_space->name(), will_be_swept); |
| } |
| } |
| |
| void MarkCompactCollector::SweepLargeSpace(LargeObjectSpace* space) { |
| auto* marking_state = heap()->non_atomic_marking_state(); |
| PtrComprCageBase cage_base(heap()->isolate()); |
| size_t surviving_object_size = 0; |
| for (auto it = space->begin(); it != space->end();) { |
| LargePage* current = *(it++); |
| HeapObject object = current->GetObject(); |
| if (!marking_state->IsMarked(object)) { |
| // Object is dead and page can be released. |
| space->RemovePage(current); |
| heap()->memory_allocator()->Free(MemoryAllocator::FreeMode::kConcurrently, |
| current); |
| |
| continue; |
| } |
| non_atomic_marking_state()->MarkBitFrom(object).Clear(); |
| current->ProgressBar().ResetIfEnabled(); |
| non_atomic_marking_state()->SetLiveBytes(current, 0); |
| surviving_object_size += static_cast<size_t>(object.Size(cage_base)); |
| } |
| space->set_objects_size(surviving_object_size); |
| } |
| |
| void MarkCompactCollector::Sweep() { |
| DCHECK(!sweeper()->sweeping_in_progress()); |
| TRACE_GC_EPOCH(heap()->tracer(), GCTracer::Scope::MC_SWEEP, |
| ThreadKind::kMain); |
| #ifdef DEBUG |
| state_ = SWEEP_SPACES; |
| #endif |
| |
| { |
| GCTracer::Scope sweep_scope(heap()->tracer(), GCTracer::Scope::MC_SWEEP_LO, |
| ThreadKind::kMain); |
| SweepLargeSpace(heap()->lo_space()); |
| } |
| { |
| GCTracer::Scope sweep_scope( |
| heap()->tracer(), GCTracer::Scope::MC_SWEEP_CODE_LO, ThreadKind::kMain); |
| SweepLargeSpace(heap()->code_lo_space()); |
| } |
| if (heap()->shared_space()) { |
| GCTracer::Scope sweep_scope(heap()->tracer(), |
| GCTracer::Scope::MC_SWEEP_SHARED_LO, |
| ThreadKind::kMain); |
| SweepLargeSpace(heap()->shared_lo_space()); |
| } |
| { |
| GCTracer::Scope sweep_scope(heap()->tracer(), GCTracer::Scope::MC_SWEEP_OLD, |
| ThreadKind::kMain); |
| StartSweepSpace(heap()->old_space()); |
| } |
| { |
| GCTracer::Scope sweep_scope( |
| heap()->tracer(), GCTracer::Scope::MC_SWEEP_CODE, ThreadKind::kMain); |
| StartSweepSpace(heap()->code_space()); |
| } |
| if (heap()->shared_space()) { |
| GCTracer::Scope sweep_scope( |
| heap()->tracer(), GCTracer::Scope::MC_SWEEP_SHARED, ThreadKind::kMain); |
| StartSweepSpace(heap()->shared_space()); |
| } |
| if (v8_flags.minor_mc && heap()->new_space()) { |
| GCTracer::Scope sweep_scope(heap()->tracer(), GCTracer::Scope::MC_SWEEP_NEW, |
| ThreadKind::kMain); |
| StartSweepNewSpace(); |
| } |
| |
| sweeper()->StartSweeping(garbage_collector_); |
| } |
| |
| namespace { |
| |
| #ifdef VERIFY_HEAP |
| |
| class YoungGenerationMarkingVerifier : public MarkingVerifier { |
| public: |
| explicit YoungGenerationMarkingVerifier(Heap* heap) |
| : MarkingVerifier(heap), |
| marking_state_(heap->non_atomic_marking_state()) {} |
| |
| const MarkingBitmap* bitmap(const MemoryChunk* chunk) override { |
| return chunk->marking_bitmap(); |
| } |
| |
| bool IsMarked(HeapObject object) override { |
| return marking_state_->IsMarked(object); |
| } |
| |
| void Run() override { |
| VerifyRoots(); |
| VerifyMarking(heap_->new_space()); |
| } |
| |
| GarbageCollector collector() const override { |
| return GarbageCollector::MINOR_MARK_COMPACTOR; |
| } |
| |
| protected: |
| void VerifyMap(Map map) override { VerifyHeapObjectImpl(map); } |
| |
| void VerifyPointers(ObjectSlot start, ObjectSlot end) override { |
| VerifyPointersImpl(start, end); |
| } |
| |
| void VerifyPointers(MaybeObjectSlot start, MaybeObjectSlot end) override { |
| VerifyPointersImpl(start, end); |
| } |
| void VerifyCodePointer(CodeObjectSlot slot) override { |
| // Code slots never appear in new space because |
| // Code objects, the only object that can contain code pointers, are |
| // always allocated in the old space. |
| UNREACHABLE(); |
| } |
| |
| void VisitCodeTarget(InstructionStream host, RelocInfo* rinfo) override { |
| InstructionStream target = |
| InstructionStream::FromTargetAddress(rinfo->target_address()); |
| VerifyHeapObjectImpl(target); |
| } |
| void VisitEmbeddedPointer(InstructionStream host, RelocInfo* rinfo) override { |
| VerifyHeapObjectImpl(rinfo->target_object(cage_base())); |
| } |
| void VerifyRootPointers(FullObjectSlot start, FullObjectSlot end) override { |
| VerifyPointersImpl(start, end); |
| } |
| |
| private: |
| V8_INLINE void VerifyHeapObjectImpl(HeapObject heap_object) { |
| CHECK_IMPLIES(Heap::InYoungGeneration(heap_object), IsMarked(heap_object)); |
| } |
| |
| template <typename TSlot> |
| V8_INLINE void VerifyPointersImpl(TSlot start, TSlot end) { |
| PtrComprCageBase cage_base = |
| GetPtrComprCageBaseFromOnHeapAddress(start.address()); |
| for (TSlot slot = start; slot < end; ++slot) { |
| typename TSlot::TObject object = slot.load(cage_base); |
| HeapObject heap_object; |
| // Minor MC treats weak references as strong. |
| if (object.GetHeapObject(&heap_object)) { |
| VerifyHeapObjectImpl(heap_object); |
| } |
| } |
| } |
| |
| NonAtomicMarkingState* const marking_state_; |
| }; |
| |
| #endif // VERIFY_HEAP |
| |
| bool IsUnmarkedObjectForYoungGeneration(Heap* heap, FullObjectSlot p) { |
| DCHECK_IMPLIES(Heap::InYoungGeneration(*p), Heap::InToPage(*p)); |
| return Heap::InYoungGeneration(*p) && |
| !heap->non_atomic_marking_state()->IsMarked(HeapObject::cast(*p)); |
| } |
| |
| } // namespace |
| |
| YoungGenerationMainMarkingVisitor::YoungGenerationMainMarkingVisitor( |
| Isolate* isolate, PtrComprCageBase cage_base, |
| MarkingWorklists::Local* worklists_local) |
| : YoungGenerationMarkingVisitorBase<YoungGenerationMainMarkingVisitor, |
| MarkingState>(isolate, worklists_local), |
| marking_state_(cage_base) {} |
| |
| MinorMarkCompactCollector::~MinorMarkCompactCollector() = default; |
| |
| void MinorMarkCompactCollector::SetUp() {} |
| |
| void MinorMarkCompactCollector::TearDown() { |
| if (heap()->incremental_marking()->IsMinorMarking()) { |
| local_marking_worklists()->Publish(); |
| heap()->main_thread_local_heap()->marking_barrier()->PublishIfNeeded(); |
| // Marking barriers of LocalHeaps will be published in their destructors. |
| marking_worklists()->Clear(); |
| } |
| } |
| |
| void MinorMarkCompactCollector::FinishConcurrentMarking() { |
| if (v8_flags.concurrent_minor_mc_marking) { |
| DCHECK_EQ(heap()->concurrent_marking()->garbage_collector(), |
| GarbageCollector::MINOR_MARK_COMPACTOR); |
| heap()->concurrent_marking()->Cancel(); |
| heap()->concurrent_marking()->FlushMemoryChunkData( |
| non_atomic_marking_state()); |
| } |
| if (auto* cpp_heap = CppHeap::From(heap_->cpp_heap())) { |
| cpp_heap->FinishConcurrentMarkingIfNeeded(); |
| } |
| } |
| |
| // static |
| constexpr size_t MinorMarkCompactCollector::kMaxParallelTasks; |
| |
| MinorMarkCompactCollector::MinorMarkCompactCollector(Heap* heap) |
| : CollectorBase(heap, GarbageCollector::MINOR_MARK_COMPACTOR), |
| sweeper_(heap_->sweeper()) {} |
| |
| std::pair<size_t, size_t> MinorMarkCompactCollector::ProcessMarkingWorklist( |
| size_t bytes_to_process) { |
| // TODO(v8:13012): Implement this later. It should be similar to |
| // MinorMarkCompactCollector::DrainMarkingWorklist. |
| UNREACHABLE(); |
| } |
| |
| void MinorMarkCompactCollector::PerformWrapperTracing() { |
| auto* cpp_heap = CppHeap::From(heap_->cpp_heap()); |
| if (!cpp_heap) return; |
| |
| DCHECK(CppHeap::From(heap_->cpp_heap())->generational_gc_supported()); |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MINOR_MC_MARK_EMBEDDER_TRACING); |
| cpp_heap->AdvanceTracing(std::numeric_limits<double>::infinity()); |
| } |
| |
| class MinorMarkCompactCollector::RootMarkingVisitor : public RootVisitor { |
| public: |
| explicit RootMarkingVisitor(MinorMarkCompactCollector* collector) |
| : collector_(collector) {} |
| |
| void VisitRootPointer(Root root, const char* description, |
| FullObjectSlot p) final { |
| MarkObjectByPointer(p); |
| } |
| |
| void VisitRootPointers(Root root, const char* description, |
| FullObjectSlot start, FullObjectSlot end) final { |
| for (FullObjectSlot p = start; p < end; ++p) { |
| DCHECK(!MapWord::IsPacked((*p).ptr())); |
| MarkObjectByPointer(p); |
| } |
| } |
| |
| GarbageCollector collector() const override { |
| return GarbageCollector::MINOR_MARK_COMPACTOR; |
| } |
| |
| private: |
| V8_INLINE void MarkObjectByPointer(FullObjectSlot p) { |
| if (!(*p).IsHeapObject()) return; |
| collector_->MarkRootObject(HeapObject::cast(*p)); |
| } |
| MinorMarkCompactCollector* const collector_; |
| }; |
| |
| void MinorMarkCompactCollector::StartMarking() { |
| #ifdef VERIFY_HEAP |
| if (v8_flags.verify_heap) { |
| for (Page* page : *heap()->new_space()) { |
| CHECK(page->marking_bitmap()->IsClean()); |
| } |
| } |
| #endif // VERIFY_HEAP |
| |
| auto* cpp_heap = CppHeap::From(heap_->cpp_heap()); |
| if (cpp_heap && cpp_heap->generational_gc_supported()) { |
| TRACE_GC(heap()->tracer(), |
| GCTracer::Scope::MINOR_MC_MARK_EMBEDDER_PROLOGUE); |
| // InitializeTracing should be called before visitor initialization in |
| // StartMarking. |
| cpp_heap->InitializeTracing(CppHeap::CollectionType::kMinor); |
| } |
| local_marking_worklists_ = std::make_unique<MarkingWorklists::Local>( |
| marking_worklists(), |
| cpp_heap ? cpp_heap->CreateCppMarkingStateForMutatorThread() |
| : MarkingWorklists::Local::kNoCppMarkingState); |
| main_marking_visitor_ = std::make_unique<YoungGenerationMainMarkingVisitor>( |
| heap()->isolate(), marking_state()->cage_base(), |
| local_marking_worklists()); |
| if (cpp_heap && cpp_heap->generational_gc_supported()) { |
| TRACE_GC(heap()->tracer(), |
| GCTracer::Scope::MINOR_MC_MARK_EMBEDDER_PROLOGUE); |
| // StartTracing immediately starts marking which requires V8 worklists to |
| // be set up. |
| cpp_heap->StartTracing(); |
| } |
| } |
| |
| void MinorMarkCompactCollector::Finish() { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MINOR_MC_FINISH); |
| |
| { |
| TRACE_GC(heap()->tracer(), |
| GCTracer::Scope::MINOR_MC_FINISH_ENSURE_CAPACITY); |
| switch (resize_new_space_) { |
| case ResizeNewSpaceMode::kShrink: |
| heap()->ReduceNewSpaceSize(); |
| break; |
| case ResizeNewSpaceMode::kGrow: |
| heap()->ExpandNewSpaceSize(); |
| break; |
| case ResizeNewSpaceMode::kNone: |
| break; |
| } |
| resize_new_space_ = ResizeNewSpaceMode::kNone; |
| |
| if (!heap()->new_space()->EnsureCurrentCapacity()) { |
| heap()->FatalProcessOutOfMemory("NewSpace::EnsureCurrentCapacity"); |
| } |
| } |
| |
| heap()->new_space()->GarbageCollectionEpilogue(); |
| |
| main_marking_visitor_->Finalize(); |
| |
| local_marking_worklists_.reset(); |
| main_marking_visitor_.reset(); |
| } |
| |
| void MinorMarkCompactCollector::CollectGarbage() { |
| DCHECK(!heap()->mark_compact_collector()->in_use()); |
| DCHECK_NOT_NULL(heap()->new_space()); |
| // Minor MC does not support processing the ephemeron remembered set. |
| DCHECK(heap()->ephemeron_remembered_set_.empty()); |
| DCHECK(!heap()->array_buffer_sweeper()->sweeping_in_progress()); |
| DCHECK(!sweeper()->AreSweeperTasksRunning()); |
| DCHECK(sweeper()->IsSweepingDoneForSpace(NEW_SPACE)); |
| |
| heap()->new_space()->FreeLinearAllocationArea(); |
| |
| MarkLiveObjects(); |
| ClearNonLiveReferences(); |
| #ifdef VERIFY_HEAP |
| if (v8_flags.verify_heap) { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MINOR_MC_MARK_VERIFY); |
| YoungGenerationMarkingVerifier verifier(heap()); |
| verifier.Run(); |
| } |
| #endif // VERIFY_HEAP |
| |
| Sweep(); |
| Finish(); |
| |
| #ifdef VERIFY_HEAP |
| // If concurrent sweeping is active, evacuation will be verified once sweeping |
| // is done using the FullEvacuationVerifier. |
| if (v8_flags.verify_heap && !sweeper()->sweeping_in_progress()) { |
| YoungGenerationEvacuationVerifier verifier(heap()); |
| verifier.Run(); |
| } |
| #endif // VERIFY_HEAP |
| |
| auto* isolate = heap()->isolate(); |
| isolate->global_handles()->UpdateListOfYoungNodes(); |
| isolate->traced_handles()->UpdateListOfYoungNodes(); |
| } |
| |
| void MinorMarkCompactCollector::MakeIterable( |
| Page* p, FreeSpaceTreatmentMode free_space_mode) { |
| CHECK(!p->IsLargePage()); |
| // We have to clear the full collectors markbits for the areas that we |
| // remove here. |
| Address free_start = p->area_start(); |
| |
| for (auto [object, size] : LiveObjectRange(p)) { |
| DCHECK(non_atomic_marking_state()->IsMarked(object)); |
| Address free_end = object.address(); |
| if (free_end != free_start) { |
| CHECK_GT(free_end, free_start); |
| size_t size = static_cast<size_t>(free_end - free_start); |
| DCHECK(heap_->non_atomic_marking_state()->bitmap(p)->AllBitsClearInRange( |
| MarkingBitmap::AddressToIndex(free_start), |
| MarkingBitmap::LimitAddressToIndex(free_end))); |
| if (free_space_mode == FreeSpaceTreatmentMode::kZapFreeSpace) { |
| ZapCode(free_start, size); |
| } |
| p->heap()->CreateFillerObjectAt(free_start, static_cast<int>(size)); |
| } |
| PtrComprCageBase cage_base(p->heap()->isolate()); |
| free_start = free_end + size; |
| } |
| |
| if (free_start != p->area_end()) { |
| CHECK_GT(p->area_end(), free_start); |
| size_t size = static_cast<size_t>(p->area_end() - free_start); |
| DCHECK(heap_->non_atomic_marking_state()->bitmap(p)->AllBitsClearInRange( |
| MarkingBitmap::AddressToIndex(free_start), |
| MarkingBitmap::LimitAddressToIndex(p->area_end()))); |
| if (free_space_mode == FreeSpaceTreatmentMode::kZapFreeSpace) { |
| ZapCode(free_start, size); |
| } |
| p->heap()->CreateFillerObjectAt(free_start, static_cast<int>(size)); |
| } |
| } |
| |
| void MinorMarkCompactCollector::ClearNonLiveReferences() { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MINOR_MC_CLEAR); |
| |
| if (V8_UNLIKELY(v8_flags.always_use_string_forwarding_table)) { |
| TRACE_GC(heap()->tracer(), |
| GCTracer::Scope::MINOR_MC_CLEAR_STRING_FORWARDING_TABLE); |
| // Clear non-live objects in the string fowarding table. |
| StringForwardingTableCleaner forwarding_table_cleaner(heap()); |
| forwarding_table_cleaner.ProcessYoungObjects(); |
| } |
| |
| Heap::ExternalStringTable& external_string_table = |
| heap()->external_string_table_; |
| if (external_string_table.HasYoung()) { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MINOR_MC_CLEAR_STRING_TABLE); |
| // Internalized strings are always stored in old space, so there is no |
| // need to clean them here. |
| ExternalStringTableCleaner<ExternalStringTableCleaningMode::kYoungOnly> |
| external_visitor(heap()); |
| external_string_table.IterateYoung(&external_visitor); |
| external_string_table.CleanUpYoung(); |
| } |
| |
| if (isolate()->global_handles()->HasYoung() || |
| isolate()->traced_handles()->HasYoung()) { |
| TRACE_GC(heap()->tracer(), |
| GCTracer::Scope::MINOR_MC_CLEAR_WEAK_GLOBAL_HANDLES); |
| isolate()->global_handles()->ProcessWeakYoungObjects( |
| nullptr, &IsUnmarkedObjectForYoungGeneration); |
| if (auto* cpp_heap = CppHeap::From(heap_->cpp_heap()); |
| cpp_heap && cpp_heap->generational_gc_supported()) { |
| isolate()->traced_handles()->ResetYoungDeadNodes( |
| &IsUnmarkedObjectForYoungGeneration); |
| } else { |
| isolate()->traced_handles()->ProcessYoungObjects( |
| nullptr, &IsUnmarkedObjectForYoungGeneration); |
| } |
| } |
| } |
| |
| class PageMarkingItem; |
| |
| YoungGenerationMarkingTask::YoungGenerationMarkingTask( |
| Isolate* isolate, Heap* heap, MarkingWorklists* global_worklists) |
| : marking_worklists_local_(std::make_unique<MarkingWorklists::Local>( |
| global_worklists, |
| heap->cpp_heap() |
| ? CppHeap::From(heap->cpp_heap())->CreateCppMarkingState() |
| : MarkingWorklists::Local::kNoCppMarkingState)), |
| visitor_(isolate, heap->marking_state()->cage_base(), |
| marking_worklists_local()) {} |
| |
| void YoungGenerationMarkingTask::MarkYoungObject(HeapObject heap_object) { |
| if (visitor_.marking_state()->TryMark(heap_object)) { |
| // Maps won't change in the atomic pause, so the map can be read without |
| // atomics. |
| Map map = Map::cast(*heap_object.map_slot()); |
| int visited_size; |
| if (Map::ObjectFieldsFrom(map.visitor_id()) == ObjectFields::kDataOnly) { |
| visited_size = heap_object.SizeFromMap(map); |
| } else { |
| visited_size = visitor_.Visit(map, heap_object); |
| } |
| if (visited_size) { |
| visitor_.marking_state()->IncrementLiveBytes( |
| MemoryChunk::cast(BasicMemoryChunk::FromHeapObject(heap_object)), |
| ALIGN_TO_ALLOCATION_ALIGNMENT(visited_size)); |
| } |
| // Objects transition to black when visited. |
| DCHECK(visitor_.marking_state()->IsMarked(heap_object)); |
| } |
| } |
| |
| void YoungGenerationMarkingTask::DrainMarkingWorklist() { |
| HeapObject heap_object; |
| while (marking_worklists_local_->Pop(&heap_object) || |
| marking_worklists_local_->PopOnHold(&heap_object)) { |
| // Maps won't change in the atomic pause, so the map can be read without |
| // atomics. |
| Map map = Map::cast(*heap_object.map_slot()); |
| // kDataOnly objects are filtered on push. |
| DCHECK_EQ(Map::ObjectFieldsFrom(map.visitor_id()), |
| ObjectFields::kMaybePointers); |
| const auto visited_size = visitor_.Visit(map, heap_object); |
| if (visited_size) { |
| visitor_.marking_state()->IncrementLiveBytes( |
| MemoryChunk::cast(BasicMemoryChunk::FromHeapObject(heap_object)), |
| ALIGN_TO_ALLOCATION_ALIGNMENT(visited_size)); |
| } |
| } |
| // Publish wrapper objects to the cppgc marking state, if registered. |
| marking_worklists_local_->PublishWrapper(); |
| } |
| |
| void YoungGenerationMarkingTask::PublishMarkingWorklist() { |
| marking_worklists_local_->Publish(); |
| } |
| |
| void YoungGenerationMarkingTask::Finalize() { visitor_.Finalize(); } |
| |
| void PageMarkingItem::Process(YoungGenerationMarkingTask* task) { |
| base::MutexGuard guard(chunk_->mutex()); |
| CodePageMemoryModificationScope memory_modification_scope(chunk_); |
| if (slots_type_ == SlotsType::kRegularSlots) { |
| MarkUntypedPointers(task); |
| } else { |
| MarkTypedPointers(task); |
| } |
| } |
| |
| void PageMarkingItem::MarkUntypedPointers(YoungGenerationMarkingTask* task) { |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.gc"), |
| "PageMarkingItem::MarkUntypedPointers"); |
| InvalidatedSlotsFilter filter = InvalidatedSlotsFilter::OldToNew( |
| chunk_, InvalidatedSlotsFilter::LivenessCheck::kNo); |
| RememberedSet<OLD_TO_NEW>::Iterate( |
| chunk_, |
| [this, task, &filter](MaybeObjectSlot slot) { |
| if (!filter.IsValid(slot.address())) return REMOVE_SLOT; |
| return CheckAndMarkObject(task, slot); |
| }, |
| SlotSet::FREE_EMPTY_BUCKETS); |
| // The invalidated slots are not needed after old-to-new slots were |
| // processed. |
| chunk_->ReleaseInvalidatedSlots<OLD_TO_NEW>(); |
| } |
| |
| void PageMarkingItem::MarkTypedPointers(YoungGenerationMarkingTask* task) { |
| TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.gc"), |
| "PageMarkingItem::MarkTypedPointers"); |
| RememberedSet<OLD_TO_NEW>::IterateTyped( |
| chunk_, [this, task](SlotType slot_type, Address slot) { |
| return UpdateTypedSlotHelper::UpdateTypedSlot( |
| heap(), slot_type, slot, [this, task](FullMaybeObjectSlot slot) { |
| return CheckAndMarkObject(task, slot); |
| }); |
| }); |
| } |
| |
| template <typename TSlot> |
| V8_INLINE SlotCallbackResult PageMarkingItem::CheckAndMarkObject( |
| YoungGenerationMarkingTask* task, TSlot slot) { |
| static_assert( |
| std::is_same<TSlot, FullMaybeObjectSlot>::value || |
| std::is_same<TSlot, MaybeObjectSlot>::value, |
| "Only FullMaybeObjectSlot and MaybeObjectSlot are expected here"); |
| MaybeObject object = *slot; |
| HeapObject heap_object; |
| if (object.GetHeapObject(&heap_object) && |
| Heap::InYoungGeneration(heap_object)) { |
| task->MarkYoungObject(heap_object); |
| return KEEP_SLOT; |
| } |
| return REMOVE_SLOT; |
| } |
| |
| void YoungGenerationMarkingJob::Run(JobDelegate* delegate) { |
| if (delegate->IsJoiningThread()) { |
| TRACE_GC(heap_->tracer(), GCTracer::Scope::MINOR_MC_MARK_PARALLEL); |
| ProcessItems(delegate); |
| } else { |
| TRACE_GC_EPOCH(heap_->tracer(), |
| GCTracer::Scope::MINOR_MC_BACKGROUND_MARKING, |
| ThreadKind::kBackground); |
| ProcessItems(delegate); |
| } |
| } |
| |
| size_t YoungGenerationMarkingJob::GetMaxConcurrency(size_t worker_count) const { |
| // Pages are not private to markers but we can still use them to estimate |
| // the amount of marking that is required. |
| const int kPagesPerTask = 2; |
| size_t items = remaining_marking_items_.load(std::memory_order_relaxed); |
| size_t num_tasks; |
| if (ShouldDrainMarkingWorklist()) { |
| num_tasks = std::max( |
| (items + 1) / kPagesPerTask, |
| global_worklists_->shared()->Size() + |
| global_worklists_->on_hold() |
| ->Size()); // TODO(v8:13012): If this is used with concurrent |
| // marking, we need to remove on_hold() here. |
| } else { |
| num_tasks = (items + 1) / kPagesPerTask; |
| } |
| |
| if (!v8_flags.parallel_marking) { |
| num_tasks = std::min<size_t>(1, num_tasks); |
| } |
| return std::min<size_t>(num_tasks, |
| MinorMarkCompactCollector::kMaxParallelTasks); |
| } |
| |
| void YoungGenerationMarkingJob::ProcessItems(JobDelegate* delegate) { |
| double marking_time = 0.0; |
| { |
| TimedScope scope(&marking_time); |
| const int task_id = delegate->GetTaskId(); |
| DCHECK_LT(task_id, tasks_.size()); |
| YoungGenerationMarkingTask& task = tasks_[task_id]; |
| ProcessMarkingItems(&task); |
| if (ShouldDrainMarkingWorklist()) { |
| task.DrainMarkingWorklist(); |
| } else { |
| task.PublishMarkingWorklist(); |
| } |
| } |
| if (v8_flags.trace_minor_mc_parallel_marking) { |
| PrintIsolate(isolate_, "marking[%p]: time=%f\n", static_cast<void*>(this), |
| marking_time); |
| } |
| } |
| |
| void YoungGenerationMarkingJob::ProcessMarkingItems( |
| YoungGenerationMarkingTask* task) { |
| // TODO(v8:13012): YoungGenerationMarkingJob is generally used to compute the |
| // transitive closure. In the context of concurrent MinorMC, it currently only |
| // seeds the worklists from the old-to-new remembered set, but does not empty |
| // them (this is done concurrently). The class should be refactored to make |
| // this clearer. |
| while (remaining_marking_items_.load(std::memory_order_relaxed) > 0) { |
| base::Optional<size_t> index = generator_.GetNext(); |
| if (!index) return; |
| for (size_t i = *index; i < marking_items_.size(); ++i) { |
| auto& work_item = marking_items_[i]; |
| if (!work_item.TryAcquire()) break; |
| work_item.Process(task); |
| if (ShouldDrainMarkingWorklist()) { |
| task->DrainMarkingWorklist(); |
| } |
| if (remaining_marking_items_.fetch_sub(1, std::memory_order_relaxed) <= |
| 1) { |
| return; |
| } |
| } |
| } |
| } |
| |
| void MinorMarkCompactCollector::MarkLiveObjectsInParallel( |
| RootMarkingVisitor* root_visitor, bool was_marked_incrementally) { |
| std::vector<PageMarkingItem> marking_items; |
| |
| // Seed the root set (roots + old->new set). |
| { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MINOR_MC_MARK_SEED); |
| isolate()->traced_handles()->ComputeWeaknessForYoungObjects( |
| &JSObject::IsUnmodifiedApiObject); |
| // MinorMC treats all weak roots except for global handles as strong. |
| // That is why we don't set skip_weak = true here and instead visit |
| // global handles separately. |
| heap()->IterateRoots(root_visitor, |
| base::EnumSet<SkipRoot>{SkipRoot::kExternalStringTable, |
| SkipRoot::kGlobalHandles, |
| SkipRoot::kTracedHandles, |
| SkipRoot::kOldGeneration, |
| SkipRoot::kReadOnlyBuiltins}); |
| isolate()->global_handles()->IterateYoungStrongAndDependentRoots( |
| root_visitor); |
| if (auto* cpp_heap = CppHeap::From(heap_->cpp_heap()); |
| cpp_heap && cpp_heap->generational_gc_supported()) { |
| // Visit the Oilpan-to-V8 remembered set. |
| isolate()->traced_handles()->IterateAndMarkYoungRootsWithOldHosts( |
| root_visitor); |
| // Visit the V8-to-Oilpan remembered set. |
| cpp_heap->VisitCrossHeapRememberedSetIfNeeded([this](JSObject obj) { |
| VisitObjectWithEmbedderFields(obj, *local_marking_worklists()); |
| }); |
| } else { |
| // Otherwise, visit all young roots. |
| isolate()->traced_handles()->IterateYoungRoots(root_visitor); |
| } |
| |
| if (!was_marked_incrementally) { |
| // Create items for each page. |
| RememberedSet<OLD_TO_NEW>::IterateMemoryChunks( |
| heap(), [&marking_items](MemoryChunk* chunk) { |
| if (chunk->slot_set<OLD_TO_NEW>()) { |
| marking_items.emplace_back( |
| chunk, PageMarkingItem::SlotsType::kRegularSlots); |
| } else { |
| chunk->ReleaseInvalidatedSlots<OLD_TO_NEW>(); |
| } |
| |
| if (chunk->typed_slot_set<OLD_TO_NEW>()) { |
| marking_items.emplace_back( |
| chunk, PageMarkingItem::SlotsType::kTypedSlots); |
| } |
| }); |
| } |
| } |
| |
| // Add tasks and run in parallel. |
| { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MINOR_MC_MARK_CLOSURE_PARALLEL); |
| |
| // CppGC starts parallel marking tasks that will trace TracedReferences. |
| if (heap_->cpp_heap()) { |
| CppHeap::From(heap_->cpp_heap()) |
| ->EnterFinalPause(heap_->embedder_stack_state_); |
| } |
| |
| // The main thread might hold local items, while GlobalPoolSize() == |
| // 0. Flush to ensure these items are visible globally and picked up |
| // by the job. |
| local_marking_worklists_->Publish(); |
| |
| std::vector<YoungGenerationMarkingTask> tasks; |
| for (size_t i = 0; i < (v8_flags.parallel_marking ? kMaxParallelTasks : 1); |
| ++i) { |
| tasks.emplace_back(isolate(), heap(), marking_worklists()); |
| } |
| V8::GetCurrentPlatform() |
| ->CreateJob( |
| v8::TaskPriority::kUserBlocking, |
| std::make_unique<YoungGenerationMarkingJob>( |
| isolate(), heap(), marking_worklists(), |
| std::move(marking_items), YoungMarkingJobType::kAtomic, tasks)) |
| ->Join(); |
| for (YoungGenerationMarkingTask& task : tasks) { |
| task.Finalize(); |
| } |
| // If unified young generation is in progress, the parallel marker may add |
| // more entries into local_marking_worklists_. |
| DCHECK_IMPLIES(!v8_flags.cppgc_young_generation, |
| local_marking_worklists_->IsEmpty()); |
| } |
| } |
| |
| void MinorMarkCompactCollector::MarkLiveObjects() { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MINOR_MC_MARK); |
| |
| const bool was_marked_incrementally = |
| !heap_->incremental_marking()->IsStopped(); |
| if (!was_marked_incrementally) { |
| StartMarking(); |
| } else { |
| TRACE_GC(heap()->tracer(), |
| GCTracer::Scope::MINOR_MC_MARK_FINISH_INCREMENTAL); |
| auto* incremental_marking = heap_->incremental_marking(); |
| DCHECK(incremental_marking->IsMinorMarking()); |
| incremental_marking->Stop(); |
| MarkingBarrier::PublishAll(heap()); |
| // TODO(v8:13012): TRACE_GC with MINOR_MC_MARK_FULL_CLOSURE_PARALLEL_JOIN. |
| // TODO(v8:13012): Instead of finishing concurrent marking here, we could |
| // continue running it to replace parallel marking. |
| FinishConcurrentMarking(); |
| } |
| |
| DCHECK_NOT_NULL(local_marking_worklists_); |
| DCHECK_NOT_NULL(main_marking_visitor_); |
| |
| RootMarkingVisitor root_visitor(this); |
| |
| MarkLiveObjectsInParallel(&root_visitor, was_marked_incrementally); |
| |
| { |
| // Finish marking the transitive closure on the main thread. |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MINOR_MC_MARK_CLOSURE); |
| if (auto* cpp_heap = CppHeap::From(heap_->cpp_heap())) { |
| cpp_heap->FinishConcurrentMarkingIfNeeded(); |
| } |
| DrainMarkingWorklist(); |
| } |
| |
| if (was_marked_incrementally) { |
| MarkingBarrier::DeactivateAll(heap()); |
| } |
| |
| if (v8_flags.minor_mc_trace_fragmentation) { |
| TraceFragmentation(); |
| } |
| } |
| |
| void MinorMarkCompactCollector::DrainMarkingWorklist() { |
| PtrComprCageBase cage_base(isolate()); |
| do { |
| PerformWrapperTracing(); |
| |
| HeapObject heap_object; |
| while (local_marking_worklists_->Pop(&heap_object)) { |
| DCHECK(!heap_object.IsFreeSpaceOrFiller(cage_base)); |
| DCHECK(heap_object.IsHeapObject()); |
| DCHECK(heap()->Contains(heap_object)); |
| DCHECK(!non_atomic_marking_state()->IsUnmarked(heap_object)); |
| // Maps won't change in the atomic pause, so the map can be read without |
| // atomics. |
| Map map = Map::cast(*heap_object.map_slot()); |
| const auto visited_size = main_marking_visitor_->Visit(map, heap_object); |
| if (visited_size) { |
| marking_state_->IncrementLiveBytes( |
| MemoryChunk::cast(BasicMemoryChunk::FromHeapObject(heap_object)), |
| visited_size); |
| } |
| } |
| } while (!IsCppHeapMarkingFinished()); |
| DCHECK(local_marking_worklists_->IsEmpty()); |
| } |
| |
| void MinorMarkCompactCollector::TraceFragmentation() { |
| NewSpace* new_space = heap()->new_space(); |
| PtrComprCageBase cage_base(isolate()); |
| const std::array<size_t, 4> free_size_class_limits = {0, 1024, 2048, 4096}; |
| size_t free_bytes_of_class[free_size_class_limits.size()] = {0}; |
| size_t live_bytes = 0; |
| size_t allocatable_bytes = 0; |
| for (Page* p : |
| PageRange(new_space->first_allocatable_address(), new_space->top())) { |
| Address free_start = p->area_start(); |
| for (auto [object, size] : LiveObjectRange(p)) { |
| Address free_end = object.address(); |
| if (free_end != free_start) { |
| size_t free_bytes = free_end - free_start; |
| int free_bytes_index = 0; |
| for (auto free_size_class_limit : free_size_class_limits) { |
| if (free_bytes >= free_size_class_limit) { |
| free_bytes_of_class[free_bytes_index] += free_bytes; |
| } |
| free_bytes_index++; |
| } |
| } |
| live_bytes += size; |
| free_start = free_end + size; |
| } |
| size_t area_end = |
| p->Contains(new_space->top()) ? new_space->top() : p->area_end(); |
| if (free_start != area_end) { |
| size_t free_bytes = area_end - free_start; |
| int free_bytes_index = 0; |
| for (auto free_size_class_limit : free_size_class_limits) { |
| if (free_bytes >= free_size_class_limit) { |
| free_bytes_of_class[free_bytes_index] += free_bytes; |
| } |
| free_bytes_index++; |
| } |
| } |
| allocatable_bytes += area_end - p->area_start(); |
| CHECK_EQ(allocatable_bytes, live_bytes + free_bytes_of_class[0]); |
| } |
| PrintIsolate(isolate(), |
| "Minor Mark-Compact Fragmentation: allocatable_bytes=%zu " |
| "live_bytes=%zu " |
| "free_bytes=%zu free_bytes_1K=%zu free_bytes_2K=%zu " |
| "free_bytes_4K=%zu\n", |
| allocatable_bytes, live_bytes, free_bytes_of_class[0], |
| free_bytes_of_class[1], free_bytes_of_class[2], |
| free_bytes_of_class[3]); |
| } |
| |
| bool MinorMarkCompactCollector::StartSweepNewSpace() { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MINOR_MC_SWEEP_NEW); |
| PagedSpaceForNewSpace* paged_space = heap()->paged_new_space()->paged_space(); |
| paged_space->ClearAllocatorState(); |
| |
| int will_be_swept = 0; |
| bool has_promoted_pages = false; |
| |
| DCHECK_EQ(Heap::ResizeNewSpaceMode::kNone, resize_new_space_); |
| resize_new_space_ = heap()->ShouldResizeNewSpace(); |
| if (resize_new_space_ == Heap::ResizeNewSpaceMode::kShrink) { |
| paged_space->StartShrinking(); |
| } |
| |
| for (auto it = paged_space->begin(); it != paged_space->end();) { |
| Page* p = *(it++); |
| DCHECK(p->SweepingDone()); |
| |
| intptr_t live_bytes_on_page = non_atomic_marking_state()->live_bytes(p); |
| if (live_bytes_on_page == 0) { |
| if (paged_space->ShouldReleaseEmptyPage()) { |
| paged_space->ReleasePage(p); |
| } else { |
| sweeper()->SweepEmptyNewSpacePage(p); |
| } |
| continue; |
| } |
| |
| if (ShouldMovePage(p, live_bytes_on_page, p->wasted_memory(), |
| MemoryReductionMode::kNone, AlwaysPromoteYoung::kNo, |
| heap()->tracer()->IsCurrentGCDueToAllocationFailure() |
| ? PromoteUnusablePages::kYes |
| : PromoteUnusablePages::kNo)) { |
| EvacuateNewSpacePageVisitor<NEW_TO_OLD>::Move(p); |
| has_promoted_pages = true; |
| sweeper()->AddPromotedPageForIteration(p); |
| } else { |
| // Page is not promoted. Sweep it instead. |
| sweeper()->AddNewSpacePage(p); |
| will_be_swept++; |
| } |
| } |
| |
| if (v8_flags.gc_verbose) { |
| PrintIsolate(isolate(), "sweeping: space=%s initialized_for_sweeping=%d", |
| paged_space->name(), will_be_swept); |
| } |
| |
| return has_promoted_pages; |
| } |
| |
| bool MinorMarkCompactCollector::SweepNewLargeSpace() { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MINOR_MC_SWEEP_NEW_LO); |
| NewLargeObjectSpace* new_lo_space = heap()->new_lo_space(); |
| DCHECK_NOT_NULL(new_lo_space); |
| |
| heap()->new_lo_space()->ResetPendingObject(); |
| |
| bool has_promoted_pages = false; |
| |
| auto* marking_state = heap()->non_atomic_marking_state(); |
| OldLargeObjectSpace* old_lo_space = heap()->lo_space(); |
| |
| for (auto it = new_lo_space->begin(); it != new_lo_space->end();) { |
| LargePage* current = *it; |
| it++; |
| HeapObject object = current->GetObject(); |
| if (!marking_state->IsMarked(object)) { |
| // Object is dead and page can be released. |
| new_lo_space->RemovePage(current); |
| heap()->memory_allocator()->Free(MemoryAllocator::FreeMode::kConcurrently, |
| current); |
| continue; |
| } |
| current->ClearFlag(MemoryChunk::TO_PAGE); |
| current->SetFlag(MemoryChunk::FROM_PAGE); |
| current->ProgressBar().ResetIfEnabled(); |
| old_lo_space->PromoteNewLargeObject(current); |
| has_promoted_pages = true; |
| sweeper()->AddPromotedPageForIteration(current); |
| } |
| new_lo_space->set_objects_size(0); |
| |
| return has_promoted_pages; |
| } |
| |
| void MinorMarkCompactCollector::Sweep() { |
| DCHECK(!sweeper()->AreSweeperTasksRunning()); |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MINOR_MC_SWEEP); |
| |
| bool has_promoted_pages = false; |
| if (StartSweepNewSpace()) has_promoted_pages = true; |
| if (SweepNewLargeSpace()) has_promoted_pages = true; |
| |
| if (v8_flags.verify_heap && has_promoted_pages) { |
| // Update the external string table in preparation for heap verification. |
| // Otherwise, updating the table will happen during the next full GC. |
| TRACE_GC(heap()->tracer(), |
| GCTracer::Scope::MINOR_MC_SWEEP_UPDATE_STRING_TABLE); |
| heap()->UpdateYoungReferencesInExternalStringTable([](Heap* heap, |
| FullObjectSlot p) { |
| DCHECK( |
| !HeapObject::cast(*p).map_word(kRelaxedLoad).IsForwardingAddress()); |
| return String::cast(*p); |
| }); |
| } |
| |
| sweeper_->StartSweeping(GarbageCollector::MINOR_MARK_COMPACTOR); |
| |
| #ifdef DEBUG |
| VerifyRememberedSetsAfterEvacuation(heap(), |
| GarbageCollector::MINOR_MARK_COMPACTOR); |
| heap()->VerifyCountersBeforeConcurrentSweeping( |
| GarbageCollector::MINOR_MARK_COMPACTOR); |
| #endif |
| |
| { |
| TRACE_GC(heap()->tracer(), GCTracer::Scope::MINOR_MC_SWEEP_START_JOBS); |
| sweeper()->StartSweeperTasks(); |
| DCHECK_EQ(0, heap_->new_lo_space()->Size()); |
| heap_->array_buffer_sweeper()->RequestSweep( |
| ArrayBufferSweeper::SweepingType::kYoung, |
| (heap_->new_space()->Size() == 0) |
| ? ArrayBufferSweeper::TreatAllYoungAsPromoted::kYes |
| : ArrayBufferSweeper::TreatAllYoungAsPromoted::kNo); |
| } |
| } |
| |
| } // namespace internal |
| } // namespace v8 |