| // Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| #include "vm/heap/incremental_compactor.h" | |
| #include "platform/assert.h" | |
| #include "vm/dart_api_state.h" | |
| #include "vm/globals.h" | |
| #include "vm/heap/become.h" | |
| #include "vm/heap/freelist.h" | |
| #include "vm/heap/heap.h" | |
| #include "vm/heap/pages.h" | |
| #include "vm/log.h" | |
| #include "vm/thread_barrier.h" | |
| #include "vm/timeline.h" | |
| #include "vm/visitor.h" | |
| namespace dart { | |
| void GCIncrementalCompactor::Prologue(PageSpace* old_space) { | |
| ASSERT(Thread::Current()->OwnsGCSafepoint()); | |
| TIMELINE_FUNCTION_GC_DURATION(Thread::Current(), "StartIncrementalCompact"); | |
| if (!SelectEvacuationCandidates(old_space)) { | |
| return; | |
| } | |
| CheckFreeLists(old_space); | |
| } | |
| bool GCIncrementalCompactor::Epilogue(PageSpace* old_space) { | |
| ASSERT(Thread::Current()->OwnsGCSafepoint()); | |
| TIMELINE_FUNCTION_GC_DURATION(Thread::Current(), "FinishIncrementalCompact"); | |
| if (!HasEvacuationCandidates(old_space)) { | |
| return false; | |
| } | |
| old_space->MakeIterable(); | |
| CheckFreeLists(old_space); | |
| CheckPreEvacuate(old_space); | |
| Evacuate(old_space); | |
| CheckPostEvacuate(old_space); | |
| CheckFreeLists(old_space); | |
| FreeEvacuatedPages(old_space); | |
| VerifyAfterIncrementalCompaction(old_space); | |
| return true; | |
| } | |
| void GCIncrementalCompactor::Abort(PageSpace* old_space) { | |
| ASSERT(Thread::Current()->OwnsGCSafepoint()); | |
| TIMELINE_FUNCTION_GC_DURATION(Thread::Current(), "AbortIncrementalCompact"); | |
| { | |
| MonitorLocker ml(old_space->tasks_lock()); | |
| switch (old_space->phase()) { | |
| case PageSpace::kDone: | |
| return; // No incremental compact in progress. | |
| case PageSpace::kSweepingRegular: | |
| case PageSpace::kSweepingLarge: | |
| // No incremental compact in progress, the page list is incomplete, and | |
| // accessing page->next is a data race. | |
| return; | |
| case PageSpace::kMarking: | |
| case PageSpace::kAwaitingFinalization: | |
| break; // Incremental compact may be in progress. | |
| default: | |
| UNREACHABLE(); | |
| } | |
| } | |
| old_space->PauseConcurrentMarking(); | |
| for (Page* page = old_space->pages_; page != nullptr; page = page->next()) { | |
| if (!page->is_evacuation_candidate()) continue; | |
| page->set_evacuation_candidate(false); | |
| uword start = page->object_start(); | |
| uword end = page->object_end(); | |
| uword current = start; | |
| while (current < end) { | |
| ObjectPtr obj = UntaggedObject::FromAddr(current); | |
| obj->untag()->ClearIsEvacuationCandidateUnsynchronized(); | |
| current += obj->untag()->HeapSize(); | |
| } | |
| } | |
| old_space->ResumeConcurrentMarking(); | |
| } | |
| struct LiveBytes { | |
| Page* page; | |
| intptr_t live_bytes; | |
| }; | |
| struct PrologueState { | |
| MallocGrowableArray<LiveBytes> pages; | |
| RelaxedAtomic<intptr_t> page_cursor; | |
| intptr_t page_limit; | |
| RelaxedAtomic<intptr_t> freelist_cursor; | |
| intptr_t freelist_limit; | |
| }; | |
| class PrologueTask : public SafepointTask { | |
| public: | |
| PrologueTask(ThreadBarrier* barrier, | |
| IsolateGroup* isolate_group, | |
| PageSpace* old_space, | |
| PrologueState* state) | |
| : SafepointTask(isolate_group, | |
| barrier, | |
| Thread::kIncrementalCompactorTask), | |
| old_space_(old_space), | |
| state_(state) {} | |
| void RunEnteredIsolateGroup() override { | |
| MarkEvacuationCandidates(); | |
| PruneFreeLists(); | |
| } | |
| void MarkEvacuationCandidates() { | |
| TIMELINE_FUNCTION_GC_DURATION(Thread::Current(), | |
| "MarkEvacuationCandidates"); | |
| for (;;) { | |
| intptr_t page_index = state_->page_cursor.fetch_add(1); | |
| if (page_index >= state_->page_limit) break; | |
| Page* page = state_->pages[page_index].page; | |
| // Already set, otherwise a barrier would be needed before moving onto | |
| // freelists. | |
| ASSERT(page->is_evacuation_candidate()); | |
| uword start = page->object_start(); | |
| uword end = page->object_end(); | |
| uword current = start; | |
| while (current < end) { | |
| ObjectPtr obj = UntaggedObject::FromAddr(current); | |
| intptr_t cid = obj->untag()->GetClassId(); | |
| if (cid != kFreeListElement && cid != kForwardingCorpse) { | |
| obj->untag()->SetIsEvacuationCandidateUnsynchronized(); | |
| } | |
| current += obj->untag()->HeapSize(); | |
| } | |
| } | |
| } | |
| void PruneFreeLists() { | |
| TIMELINE_FUNCTION_GC_DURATION(Thread::Current(), "PruneFreeLists"); | |
| for (;;) { | |
| intptr_t chunk = state_->freelist_cursor.fetch_add(1); | |
| if (chunk >= state_->freelist_limit) break; | |
| intptr_t list_index = chunk / (FreeList::kNumLists + 1); | |
| intptr_t size_class_index = chunk % (FreeList::kNumLists + 1); | |
| FreeList* freelist = &old_space_->freelists_[list_index]; | |
| // Empty bump-region, no need to prune this. | |
| ASSERT(freelist->top_ == freelist->end_); | |
| FreeListElement* current = freelist->free_lists_[size_class_index]; | |
| freelist->free_lists_[size_class_index] = nullptr; | |
| while (current != nullptr) { | |
| FreeListElement* next = current->next(); | |
| if (!Page::Of(current)->is_evacuation_candidate()) { | |
| current->set_next(freelist->free_lists_[size_class_index]); | |
| freelist->free_lists_[size_class_index] = current; | |
| } | |
| current = next; | |
| } | |
| } | |
| } | |
| private: | |
| PageSpace* old_space_; | |
| PrologueState* state_; | |
| DISALLOW_COPY_AND_ASSIGN(PrologueTask); | |
| }; | |
| bool GCIncrementalCompactor::SelectEvacuationCandidates(PageSpace* old_space) { | |
| // Only evacuate pages that are at least half empty. | |
| constexpr intptr_t kEvacuationThreshold = Page::kPageSize / 2; | |
| // Evacuate no more than this amount of objects. This puts a bound on the | |
| // stop-the-world evacuate step that is similar to the existing longest | |
| // stop-the-world step of the scavenger. | |
| const intptr_t kMaxEvacuatedBytes = | |
| (old_space->heap_->new_space()->ThresholdInWords() << kWordSizeLog2) / 4; | |
| PrologueState state; | |
| { | |
| TIMELINE_FUNCTION_GC_DURATION(Thread::Current(), | |
| "SelectEvacuationCandidates"); | |
| for (Page* page = old_space->pages_; page != nullptr; page = page->next()) { | |
| if (page->is_never_evacuate()) continue; | |
| intptr_t live_bytes = page->live_bytes(); | |
| if (live_bytes > kEvacuationThreshold) continue; | |
| state.pages.Add({page, live_bytes}); | |
| } | |
| state.pages.Sort([](const LiveBytes* a, const LiveBytes* b) -> int { | |
| if (a->live_bytes < b->live_bytes) return -1; | |
| if (a->live_bytes > b->live_bytes) return 1; | |
| return 0; | |
| }); | |
| intptr_t num_candidates = 0; | |
| intptr_t cumulative_live_bytes = 0; | |
| for (intptr_t i = 0; i < state.pages.length(); i++) { | |
| intptr_t live_bytes = state.pages[i].live_bytes; | |
| if (cumulative_live_bytes + live_bytes <= kMaxEvacuatedBytes) { | |
| num_candidates++; | |
| cumulative_live_bytes += live_bytes; | |
| state.pages[i].page->set_evacuation_candidate(true); | |
| } | |
| } | |
| #if defined(SUPPORT_TIMELINE) | |
| tbes.SetNumArguments(2); | |
| tbes.FormatArgument(0, "cumulative_live_bytes", "%" Pd, | |
| cumulative_live_bytes); | |
| tbes.FormatArgument(1, "num_candidates", "%" Pd, num_candidates); | |
| #endif | |
| state.page_cursor = 0; | |
| state.page_limit = num_candidates; | |
| state.freelist_cursor = | |
| PageSpace::kDataFreelist * (FreeList::kNumLists + 1); | |
| state.freelist_limit = | |
| old_space->num_freelists_ * (FreeList::kNumLists + 1); | |
| if (num_candidates == 0) return false; | |
| } | |
| old_space->ReleaseBumpAllocation(); | |
| IsolateGroup* isolate_group = IsolateGroup::Current(); | |
| const intptr_t num_tasks = | |
| isolate_group->heap()->new_space()->NumScavengeWorkers(); | |
| RELEASE_ASSERT(num_tasks > 0); | |
| ThreadBarrier* barrier = new ThreadBarrier(num_tasks, /*initial=*/1); | |
| IntrusiveDList<SafepointTask> tasks; | |
| for (intptr_t i = 0; i < num_tasks; i++) { | |
| tasks.Append(new PrologueTask(barrier, isolate_group, old_space, &state)); | |
| } | |
| isolate_group->safepoint_handler()->RunTasks(&tasks); | |
| for (intptr_t i = PageSpace::kDataFreelist, n = old_space->num_freelists_; | |
| i < n; i++) { | |
| FreeList* freelist = &old_space->freelists_[i]; | |
| ASSERT(freelist->top_ == freelist->end_); | |
| freelist->free_map_.Reset(); | |
| for (intptr_t j = 0; j < FreeList::kNumLists; j++) { | |
| freelist->free_map_.Set(j, freelist->free_lists_[j] != nullptr); | |
| } | |
| } | |
| return true; | |
| } | |
| // Free lists should not contain any evacuation candidates. | |
| void GCIncrementalCompactor::CheckFreeLists(PageSpace* old_space) { | |
| #if defined(DEBUG) | |
| for (intptr_t i = 0, n = old_space->num_freelists_; i < n; i++) { | |
| FreeList* freelist = &old_space->freelists_[i]; | |
| if (freelist->top_ < freelist->end_) { | |
| Page* page = Page::Of(freelist->top_); | |
| ASSERT(!page->is_evacuation_candidate()); | |
| } | |
| for (intptr_t j = 0; j <= FreeList::kNumLists; j++) { | |
| FreeListElement* current = freelist->free_lists_[j]; | |
| while (current != nullptr) { | |
| Page* page = Page::Of(reinterpret_cast<uword>(current)); | |
| ASSERT(!page->is_evacuation_candidate()); | |
| current = current->next(); | |
| } | |
| } | |
| } | |
| #endif | |
| } | |
| static void objcpy(void* dst, const void* src, size_t size) { | |
| uword* __restrict dst_cursor = reinterpret_cast<uword*>(dst); | |
| const uword* __restrict src_cursor = reinterpret_cast<const uword*>(src); | |
| do { | |
| uword a = *src_cursor++; | |
| uword b = *src_cursor++; | |
| *dst_cursor++ = a; | |
| *dst_cursor++ = b; | |
| size -= (2 * sizeof(uword)); | |
| } while (size > 0); | |
| } | |
| bool GCIncrementalCompactor::HasEvacuationCandidates(PageSpace* old_space) { | |
| for (Page* page = old_space->pages_; page != nullptr; page = page->next()) { | |
| if (page->is_evacuation_candidate()) return true; | |
| } | |
| return false; | |
| } | |
| void GCIncrementalCompactor::CheckPreEvacuate(PageSpace* old_space) { | |
| if (!FLAG_verify_before_gc) return; | |
| TIMELINE_FUNCTION_GC_DURATION(Thread::Current(), "CheckPreEvacuate"); | |
| // Check evacuation candidate pages have evacuation candidate objects or free | |
| // space. I.e., we didn't allocate into it after selecting it as an evacuation | |
| // candidate. | |
| for (Page* page = old_space->pages_; page != nullptr; page = page->next()) { | |
| if (page->is_evacuation_candidate()) { | |
| uword start = page->object_start(); | |
| uword end = page->object_end(); | |
| uword current = start; | |
| while (current < end) { | |
| ObjectPtr obj = UntaggedObject::FromAddr(current); | |
| intptr_t size = obj->untag()->HeapSize(); | |
| ASSERT(obj->untag()->IsEvacuationCandidate() || | |
| obj->untag()->GetClassId() == kFreeListElement || | |
| obj->untag()->GetClassId() == kForwardingCorpse); | |
| current += size; | |
| } | |
| } | |
| } | |
| // Check non-evac pages don't have evac candidates. | |
| for (Page* page = old_space->pages_; page != nullptr; page = page->next()) { | |
| if (!page->is_evacuation_candidate()) { | |
| uword start = page->object_start(); | |
| uword end = page->object_end(); | |
| uword current = start; | |
| while (current < end) { | |
| ObjectPtr obj = UntaggedObject::FromAddr(current); | |
| intptr_t size = obj->untag()->HeapSize(); | |
| ASSERT(!obj->untag()->IsEvacuationCandidate()); | |
| current += size; | |
| } | |
| } | |
| } | |
| } | |
| class IncrementalForwardingVisitor : public ObjectPointerVisitor, | |
| public PredicateObjectPointerVisitor, | |
| public ObjectVisitor, | |
| public HandleVisitor { | |
| public: | |
| explicit IncrementalForwardingVisitor(Thread* thread) | |
| : ObjectPointerVisitor(thread->isolate_group()), HandleVisitor() {} | |
| void VisitObject(ObjectPtr obj) override { | |
| if (obj->untag()->IsMarked()) { | |
| obj->untag()->VisitPointers(this); | |
| } | |
| } | |
| void VisitPointers(ObjectPtr* first, ObjectPtr* last) override { | |
| PredicateVisitPointers(first, last); | |
| } | |
| bool PredicateVisitPointers(ObjectPtr* first, ObjectPtr* last) override { | |
| bool has_new_target = false; | |
| for (ObjectPtr* ptr = first; ptr <= last; ptr++) { | |
| ObjectPtr target = *ptr; | |
| if (target->IsImmediateObject()) continue; | |
| if (target->IsNewObject()) { | |
| has_new_target = true; | |
| continue; | |
| } | |
| if (target->IsForwardingCorpse()) { | |
| ASSERT(!target->untag()->IsMarked()); | |
| ASSERT(!target->untag()->IsEvacuationCandidate()); | |
| uword addr = UntaggedObject::ToAddr(target); | |
| ForwardingCorpse* forwarder = reinterpret_cast<ForwardingCorpse*>(addr); | |
| *ptr = forwarder->target(); | |
| } else { | |
| ASSERT(target->untag()->IsMarked()); | |
| ASSERT(!target->untag()->IsEvacuationCandidate()); | |
| } | |
| } | |
| return has_new_target; | |
| } | |
| #if defined(DART_COMPRESSED_POINTERS) | |
| void VisitCompressedPointers(uword heap_base, | |
| CompressedObjectPtr* first, | |
| CompressedObjectPtr* last) override { | |
| PredicateVisitCompressedPointers(heap_base, first, last); | |
| } | |
| bool PredicateVisitCompressedPointers(uword heap_base, | |
| CompressedObjectPtr* first, | |
| CompressedObjectPtr* last) override { | |
| bool has_new_target = false; | |
| for (CompressedObjectPtr* ptr = first; ptr <= last; ptr++) { | |
| ObjectPtr target = ptr->Decompress(heap_base); | |
| if (target->IsImmediateObject()) continue; | |
| if (target->IsNewObject()) { | |
| has_new_target = true; | |
| continue; | |
| } | |
| if (target->IsForwardingCorpse()) { | |
| ASSERT(!target->untag()->IsMarked()); | |
| ASSERT(!target->untag()->IsEvacuationCandidate()); | |
| uword addr = UntaggedObject::ToAddr(target); | |
| ForwardingCorpse* forwarder = reinterpret_cast<ForwardingCorpse*>(addr); | |
| *ptr = forwarder->target(); | |
| } else { | |
| ASSERT(target->untag()->IsMarked()); | |
| ASSERT(!target->untag()->IsEvacuationCandidate()); | |
| } | |
| } | |
| return has_new_target; | |
| } | |
| #endif | |
| void VisitHandle(uword addr) override { | |
| FinalizablePersistentHandle* handle = | |
| reinterpret_cast<FinalizablePersistentHandle*>(addr); | |
| ObjectPtr target = handle->ptr(); | |
| if (target->IsHeapObject() && target->IsForwardingCorpse()) { | |
| uword addr = UntaggedObject::ToAddr(target); | |
| ForwardingCorpse* forwarder = reinterpret_cast<ForwardingCorpse*>(addr); | |
| *handle->ptr_addr() = forwarder->target(); | |
| } | |
| } | |
| void VisitTypedDataViewPointers(TypedDataViewPtr view, | |
| CompressedObjectPtr* first, | |
| CompressedObjectPtr* last) override { | |
| ObjectPtr old_backing = view->untag()->typed_data(); | |
| VisitCompressedPointers(view->heap_base(), first, last); | |
| ObjectPtr new_backing = view->untag()->typed_data(); | |
| const bool backing_moved = old_backing != new_backing; | |
| if (backing_moved) { | |
| typed_data_views_.Add(view); | |
| } | |
| } | |
| bool CanVisitSuspendStatePointers(SuspendStatePtr suspend_state) override { | |
| if ((suspend_state->untag()->pc() != 0) && !can_visit_stack_frames_) { | |
| // Visiting pointers of SuspendState objects with copied stack frame | |
| // needs to query stack map, which can touch other Dart objects | |
| // (such as GrowableObjectArray of InstructionsTable). | |
| // Those objects may have an inconsistent state during compaction, | |
| // so processing of SuspendState objects is postponed to the later | |
| // stage of compaction. | |
| suspend_states_.Add(suspend_state); | |
| return false; | |
| } | |
| return true; | |
| } | |
| void UpdateViews() { | |
| const intptr_t length = typed_data_views_.length(); | |
| for (intptr_t i = 0; i < length; ++i) { | |
| auto raw_view = typed_data_views_[i]; | |
| const classid_t cid = raw_view->untag()->typed_data()->GetClassId(); | |
| // If we have external typed data we can simply return, since the backing | |
| // store lives in C-heap and will not move. Otherwise we have to update | |
| // the inner pointer. | |
| if (IsTypedDataClassId(cid)) { | |
| raw_view->untag()->RecomputeDataFieldForInternalTypedData(); | |
| } else { | |
| ASSERT(IsExternalTypedDataClassId(cid)); | |
| } | |
| } | |
| } | |
| void UpdateSuspendStates() { | |
| can_visit_stack_frames_ = true; | |
| const intptr_t length = suspend_states_.length(); | |
| for (intptr_t i = 0; i < length; ++i) { | |
| auto suspend_state = suspend_states_[i]; | |
| suspend_state->untag()->VisitPointers(this); | |
| } | |
| } | |
| private: | |
| bool can_visit_stack_frames_ = false; | |
| MallocGrowableArray<TypedDataViewPtr> typed_data_views_; | |
| MallocGrowableArray<SuspendStatePtr> suspend_states_; | |
| DISALLOW_COPY_AND_ASSIGN(IncrementalForwardingVisitor); | |
| }; | |
| class StoreBufferForwardingVisitor : public ObjectPointerVisitor { | |
| public: | |
| StoreBufferForwardingVisitor(IsolateGroup* isolate_group, | |
| IncrementalForwardingVisitor* visitor) | |
| : ObjectPointerVisitor(isolate_group), visitor_(visitor) {} | |
| void VisitPointers(ObjectPtr* first, ObjectPtr* last) override { | |
| for (ObjectPtr* ptr = first; ptr <= last; ptr++) { | |
| ObjectPtr obj = *ptr; | |
| ASSERT(!obj->IsImmediateOrNewObject()); | |
| if (obj->IsForwardingCorpse()) { | |
| ASSERT(!obj->untag()->IsMarked()); | |
| ASSERT(!obj->untag()->IsEvacuationCandidate()); | |
| uword addr = UntaggedObject::ToAddr(obj); | |
| ForwardingCorpse* forwarder = reinterpret_cast<ForwardingCorpse*>(addr); | |
| obj = forwarder->target(); | |
| *ptr = obj; | |
| } else { | |
| ASSERT(obj->untag()->IsMarked()); | |
| ASSERT(!obj->untag()->IsEvacuationCandidate()); | |
| } | |
| visitor_->VisitObject(obj); | |
| } | |
| } | |
| #if defined(DART_COMPRESSED_POINTERS) | |
| void VisitCompressedPointers(uword heap_base, | |
| CompressedObjectPtr* first, | |
| CompressedObjectPtr* last) override { | |
| UNREACHABLE(); // Store buffer blocks are not compressed. | |
| } | |
| #endif | |
| private: | |
| IncrementalForwardingVisitor* visitor_; | |
| DISALLOW_COPY_AND_ASSIGN(StoreBufferForwardingVisitor); | |
| }; | |
| class EpilogueState { | |
| public: | |
| EpilogueState(Page* evac_page, | |
| StoreBufferBlock* block, | |
| Page* new_page, | |
| Mutex* pages_lock) | |
| : evac_page_(evac_page), | |
| block_(block), | |
| new_page_(new_page), | |
| pages_lock_(pages_lock) {} | |
| bool NextEvacPage(Page** page) { | |
| // Needs to be the old_space pages lock since evacuation may also allocate | |
| // new pages and race with page->next_. | |
| MutexLocker ml(pages_lock_); | |
| while (evac_page_ != nullptr) { | |
| Page* current = evac_page_; | |
| evac_page_ = current->next(); | |
| if (current->is_evacuation_candidate()) { | |
| *page = current; | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| bool NextBlock(StoreBufferBlock** block) { | |
| MutexLocker ml(pages_lock_); | |
| if (block_ != nullptr) { | |
| StoreBufferBlock* current = block_; | |
| block_ = current->next(); | |
| current->set_next(nullptr); | |
| *block = current; | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool NextNewPage(Page** page) { | |
| MutexLocker ml(pages_lock_); | |
| if (new_page_ != nullptr) { | |
| Page* current = new_page_; | |
| new_page_ = current->next(); | |
| *page = current; | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool TakeOOM() { return oom_slice_.exchange(false); } | |
| bool TakeWeakHandles() { return weak_handles_slice_.exchange(false); } | |
| bool TakeWeakTables() { return weak_tables_slice_.exchange(false); } | |
| bool TakeRoots() { return roots_slice_.exchange(false); } | |
| bool TakeResetProgressBars() { | |
| return reset_progress_bars_slice_.exchange(false); | |
| } | |
| void AddNewFreeSize(intptr_t size) { new_free_size_ += size; } | |
| intptr_t NewFreeSize() { return new_free_size_; } | |
| private: | |
| Page* evac_page_; | |
| StoreBufferBlock* block_; | |
| Page* new_page_; | |
| Mutex* pages_lock_; | |
| RelaxedAtomic<bool> oom_slice_ = {true}; | |
| RelaxedAtomic<bool> weak_handles_slice_ = {true}; | |
| RelaxedAtomic<bool> weak_tables_slice_ = {true}; | |
| RelaxedAtomic<bool> id_ring_slice_ = {true}; | |
| RelaxedAtomic<bool> roots_slice_ = {true}; | |
| RelaxedAtomic<bool> reset_progress_bars_slice_ = {true}; | |
| RelaxedAtomic<intptr_t> new_free_size_ = {0}; | |
| }; | |
| class EpilogueTask : public SafepointTask { | |
| public: | |
| EpilogueTask(ThreadBarrier* barrier, | |
| IsolateGroup* isolate_group, | |
| Scavenger* new_space, | |
| PageSpace* old_space, | |
| FreeList* freelist, | |
| EpilogueState* state) | |
| : SafepointTask(isolate_group, | |
| barrier, | |
| Thread::kIncrementalCompactorTask), | |
| new_space_(new_space), | |
| old_space_(old_space), | |
| freelist_(freelist), | |
| state_(state) {} | |
| void RunEnteredIsolateGroup() override { | |
| Thread* thread = Thread::Current(); | |
| Evacuate(); | |
| barrier_->Sync(); | |
| IncrementalForwardingVisitor visitor(thread); | |
| if (state_->TakeOOM()) { | |
| old_space_->VisitRoots(&visitor); // OOM reservation. | |
| } | |
| ForwardStoreBuffer(&visitor); | |
| ForwardRememberedCards(&visitor); | |
| ForwardNewSpace(&visitor); | |
| if (state_->TakeWeakHandles()) { | |
| TIMELINE_FUNCTION_GC_DURATION(thread, "WeakPersistentHandles"); | |
| isolate_group_->VisitWeakPersistentHandles(&visitor); | |
| } | |
| if (state_->TakeWeakTables()) { | |
| TIMELINE_FUNCTION_GC_DURATION(thread, "WeakTables"); | |
| isolate_group_->heap()->ForwardWeakTables(&visitor); | |
| } | |
| barrier_->Sync(); | |
| { | |
| // After forwarding the heap because visits each view's underyling buffer. | |
| TIMELINE_FUNCTION_GC_DURATION(thread, "Views"); | |
| visitor.UpdateViews(); | |
| } | |
| if (state_->TakeRoots()) { | |
| // After forwarding the heap because visiting the stack requires stackmaps | |
| // to already be forwarded. | |
| TIMELINE_FUNCTION_GC_DURATION(thread, "Roots"); | |
| isolate_group_->VisitObjectPointers( | |
| &visitor, ValidationPolicy::kDontValidateFrames); | |
| } | |
| barrier_->Sync(); | |
| { | |
| // After processing the object store because of the dependency on | |
| // canonicalized_stack_map_entries. | |
| TIMELINE_FUNCTION_GC_DURATION(thread, "SuspendStates"); | |
| visitor.UpdateSuspendStates(); | |
| } | |
| if (state_->TakeResetProgressBars()) { | |
| // After ForwardRememberedCards. | |
| old_space_->ResetProgressBars(); | |
| } | |
| } | |
| void Evacuate() { | |
| TIMELINE_FUNCTION_GC_DURATION(Thread::Current(), "Evacuate"); | |
| old_space_->AcquireLock(freelist_); | |
| bool any_failed = false; | |
| intptr_t bytes_evacuated = 0; | |
| Page* page; | |
| while (state_->NextEvacPage(&page)) { | |
| ASSERT(page->is_evacuation_candidate()); | |
| bool page_failed = false; | |
| uword start = page->object_start(); | |
| uword end = page->object_end(); | |
| uword current = start; | |
| while (current < end) { | |
| ObjectPtr obj = UntaggedObject::FromAddr(current); | |
| intptr_t size = obj->untag()->HeapSize(); | |
| if (obj->untag()->IsMarked()) { | |
| uword copied = old_space_->TryAllocatePromoLocked(freelist_, size); | |
| if (copied == 0) { | |
| obj->untag()->ClearIsEvacuationCandidateUnsynchronized(); | |
| page_failed = true; | |
| any_failed = true; | |
| } else { | |
| ASSERT(!Page::Of(copied)->is_evacuation_candidate()); | |
| bytes_evacuated += size; | |
| objcpy(reinterpret_cast<void*>(copied), | |
| reinterpret_cast<const void*>(current), size); | |
| ObjectPtr copied_obj = UntaggedObject::FromAddr(copied); | |
| copied_obj->untag()->ClearIsEvacuationCandidateUnsynchronized(); | |
| if (IsTypedDataClassId(copied_obj->GetClassIdOfHeapObject())) { | |
| static_cast<TypedDataPtr>(copied_obj) | |
| ->untag() | |
| ->RecomputeDataField(); | |
| } | |
| ForwardingCorpse::AsForwarder(current, size) | |
| ->set_target(copied_obj); | |
| } | |
| } | |
| current += size; | |
| } | |
| if (page_failed) { | |
| page->set_evacuation_candidate(false); | |
| } | |
| } | |
| old_space_->ReleaseLock(freelist_); | |
| old_space_->usage_.used_in_words -= (bytes_evacuated >> kWordSizeLog2); | |
| #if defined(SUPPORT_TIMELINE) | |
| tbes.SetNumArguments(1); | |
| tbes.FormatArgument(0, "bytes_evacuated", "%" Pd, bytes_evacuated); | |
| #endif | |
| if (any_failed) { | |
| OS::PrintErr("evacuation failed\n"); | |
| } | |
| } | |
| void ForwardStoreBuffer(IncrementalForwardingVisitor* visitor) { | |
| TIMELINE_FUNCTION_GC_DURATION(Thread::Current(), "ForwardStoreBuffer"); | |
| StoreBufferForwardingVisitor store_visitor(isolate_group_, visitor); | |
| StoreBuffer* store_buffer = isolate_group_->store_buffer(); | |
| StoreBufferBlock* block; | |
| while (state_->NextBlock(&block)) { | |
| // Generated code appends to store buffers; tell MemorySanitizer. | |
| MSAN_UNPOISON(block, sizeof(*block)); | |
| block->VisitObjectPointers(&store_visitor); | |
| store_buffer->PushBlock(block, StoreBuffer::kIgnoreThreshold); | |
| } | |
| } | |
| void ForwardRememberedCards(IncrementalForwardingVisitor* visitor) { | |
| TIMELINE_FUNCTION_GC_DURATION(Thread::Current(), "ForwardRememberedCards"); | |
| for (Page* page = old_space_->large_pages_; page != nullptr; | |
| page = page->next()) { | |
| page->VisitRememberedCards(visitor, /*only_marked*/ true); | |
| } | |
| } | |
| void ForwardNewSpace(IncrementalForwardingVisitor* visitor) { | |
| TIMELINE_FUNCTION_GC_DURATION(Thread::Current(), "ForwardNewSpace"); | |
| Page* page; | |
| while (state_->NextNewPage(&page)) { | |
| ForwardAndSweepNewPage(visitor, page); | |
| } | |
| new_space_->AddSweepResults(free_, free_head_, free_tail_); | |
| } | |
| DART_NOINLINE | |
| void ForwardAndSweepNewPage(IncrementalForwardingVisitor* visitor, | |
| Page* page) { | |
| ASSERT(!page->is_image()); | |
| ASSERT(!page->is_old()); | |
| ASSERT(!page->is_executable()); | |
| uword start = page->object_start(); | |
| uword end = page->object_end(); | |
| uword current = start; | |
| while (current < end) { | |
| ObjectPtr raw_obj = UntaggedObject::FromAddr(current); | |
| ASSERT(Page::Of(raw_obj) == page); | |
| uword tags = raw_obj->untag()->tags(); | |
| intptr_t obj_size = raw_obj->untag()->HeapSize(tags); | |
| if (UntaggedObject::IsMarked(tags)) { | |
| raw_obj->untag()->ClearMarkBitUnsynchronized(); | |
| ASSERT(Heap::IsAllocatableInNewSpace(obj_size)); | |
| raw_obj->untag()->VisitPointers(visitor); | |
| } else { | |
| uword free_end = current + obj_size; | |
| while (free_end < end) { | |
| ObjectPtr next_obj = UntaggedObject::FromAddr(free_end); | |
| tags = next_obj->untag()->tags(); | |
| if (UntaggedObject::IsMarked(tags)) { | |
| // Reached the end of the free block. | |
| break; | |
| } | |
| // Expand the free block by the size of this object. | |
| free_end += next_obj->untag()->HeapSize(tags); | |
| } | |
| obj_size = free_end - current; | |
| #if defined(DEBUG) | |
| memset(reinterpret_cast<void*>(current), Heap::kZapByte, obj_size); | |
| #endif // DEBUG | |
| free_ += obj_size; | |
| FreeListElement* element = | |
| FreeListElement::AsElementNew(current, obj_size); | |
| if (obj_size >= Scavenger::kMinTlabReuseSize) { | |
| element->set_next(free_head_); | |
| free_head_ = element; | |
| if (free_tail_ == nullptr) free_tail_ = element; | |
| } | |
| } | |
| current += obj_size; | |
| } | |
| } | |
| private: | |
| Scavenger* new_space_; | |
| PageSpace* old_space_; | |
| FreeList* freelist_; | |
| EpilogueState* state_; | |
| intptr_t free_ = 0; | |
| FreeListElement* free_head_ = nullptr; | |
| FreeListElement* free_tail_ = nullptr; | |
| }; | |
| void GCIncrementalCompactor::Evacuate(PageSpace* old_space) { | |
| IsolateGroup* isolate_group = IsolateGroup::Current(); | |
| Scavenger* new_space = old_space->heap_->new_space(); | |
| isolate_group->ReleaseStoreBuffers(); | |
| isolate_group->AbandonRemainingTLABs(new_space); | |
| new_space->ResetForSweep(); | |
| EpilogueState state( | |
| old_space->pages_, isolate_group->store_buffer()->PopAll(), | |
| old_space->heap_->new_space()->head(), &old_space->pages_lock_); | |
| // This must use NumScavengeWorkers because that determines the number of | |
| // freelists available for workers. | |
| const intptr_t num_tasks = | |
| isolate_group->heap()->new_space()->NumScavengeWorkers(); | |
| RELEASE_ASSERT(num_tasks > 0); | |
| ThreadBarrier* barrier = new ThreadBarrier(num_tasks, /*initial=*/1); | |
| IntrusiveDList<SafepointTask> tasks; | |
| for (intptr_t i = 0; i < num_tasks; i++) { | |
| tasks.Append(new EpilogueTask(barrier, isolate_group, new_space, old_space, | |
| old_space->DataFreeList(i), &state)); | |
| } | |
| isolate_group->safepoint_handler()->RunTasks(&tasks); | |
| } | |
| void GCIncrementalCompactor::CheckPostEvacuate(PageSpace* old_space) { | |
| if (!FLAG_verify_after_gc) return; | |
| TIMELINE_FUNCTION_GC_DURATION(Thread::Current(), "CheckPostEvacuate"); | |
| // Check there are no remaining evac candidates | |
| for (Page* page = old_space->pages_; page != nullptr; page = page->next()) { | |
| uword start = page->object_start(); | |
| uword end = page->object_end(); | |
| uword current = start; | |
| while (current < end) { | |
| ObjectPtr obj = UntaggedObject::FromAddr(current); | |
| intptr_t size = obj->untag()->HeapSize(); | |
| ASSERT(!obj->untag()->IsEvacuationCandidate() || | |
| !obj->untag()->IsMarked()); | |
| current += size; | |
| } | |
| } | |
| } | |
| void GCIncrementalCompactor::FreeEvacuatedPages(PageSpace* old_space) { | |
| Page* prev_page = nullptr; | |
| Page* page = old_space->pages_; | |
| while (page != nullptr) { | |
| Page* next_page = page->next(); | |
| if (page->is_evacuation_candidate()) { | |
| old_space->FreePage(page, prev_page); | |
| } else { | |
| prev_page = page; | |
| } | |
| page = next_page; | |
| } | |
| } | |
| class VerifyAfterIncrementalCompactionVisitor : public ObjectVisitor, | |
| public ObjectPointerVisitor { | |
| public: | |
| VerifyAfterIncrementalCompactionVisitor() | |
| : ObjectVisitor(), ObjectPointerVisitor(IsolateGroup::Current()) {} | |
| void VisitObject(ObjectPtr obj) override { | |
| // New-space has been swept, but old-space has not. | |
| if (obj->IsNewObject()) { | |
| if (obj->untag()->GetClassId() != kFreeListElement) { | |
| current_ = obj; | |
| obj->untag()->VisitPointers(this); | |
| } | |
| } else { | |
| if (obj->untag()->IsMarked()) { | |
| current_ = obj; | |
| obj->untag()->VisitPointers(this); | |
| } | |
| } | |
| } | |
| void VisitPointers(ObjectPtr* from, ObjectPtr* to) override { | |
| for (ObjectPtr* ptr = from; ptr <= to; ptr++) { | |
| ObjectPtr obj = *ptr; | |
| if (!obj->IsHeapObject()) continue; | |
| if (obj->IsForwardingCorpse() || obj->IsFreeListElement() || | |
| (obj->IsOldObject() && !obj->untag()->IsMarked())) { | |
| OS::PrintErr("object=0x%" Px ", slot=0x%" Px ", value=0x%" Px "\n", | |
| static_cast<uword>(current_), reinterpret_cast<uword>(ptr), | |
| static_cast<uword>(obj)); | |
| failed_ = true; | |
| } | |
| } | |
| } | |
| #if defined(DART_COMPRESSED_POINTERS) | |
| void VisitCompressedPointers(uword heap_base, | |
| CompressedObjectPtr* from, | |
| CompressedObjectPtr* to) override { | |
| for (CompressedObjectPtr* ptr = from; ptr <= to; ptr++) { | |
| ObjectPtr obj = ptr->Decompress(heap_base); | |
| if (!obj->IsHeapObject()) continue; | |
| if (obj->IsForwardingCorpse() || obj->IsFreeListElement() || | |
| (obj->IsOldObject() && !obj->untag()->IsMarked())) { | |
| OS::PrintErr("object=0x%" Px ", slot=0x%" Px ", value=0x%" Px "\n", | |
| static_cast<uword>(current_), reinterpret_cast<uword>(ptr), | |
| static_cast<uword>(obj)); | |
| failed_ = true; | |
| } | |
| } | |
| } | |
| #endif | |
| bool failed() const { return failed_; } | |
| private: | |
| ObjectPtr current_; | |
| bool failed_ = false; | |
| DISALLOW_COPY_AND_ASSIGN(VerifyAfterIncrementalCompactionVisitor); | |
| }; | |
| void GCIncrementalCompactor::VerifyAfterIncrementalCompaction( | |
| PageSpace* old_space) { | |
| if (!FLAG_verify_after_gc) return; | |
| TIMELINE_FUNCTION_GC_DURATION(Thread::Current(), | |
| "VerifyAfterIncrementalCompaction"); | |
| VerifyAfterIncrementalCompactionVisitor visitor; | |
| old_space->heap_->VisitObjects(&visitor); | |
| if (visitor.failed()) { | |
| FATAL("verify after incremental compact"); | |
| } | |
| } | |
| } // namespace dart |