| // 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 = 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 TakeIdRing() { return id_ring_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, | 
 |                PageSpace* old_space, | 
 |                FreeList* freelist, | 
 |                EpilogueState* state) | 
 |       : SafepointTask(isolate_group, | 
 |                       barrier, | 
 |                       Thread::kIncrementalCompactorTask), | 
 |         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); | 
 |     } | 
 | #ifndef PRODUCT | 
 |     if (state_->TakeIdRing()) { | 
 |       TIMELINE_FUNCTION_GC_DURATION(thread, "IdRing"); | 
 |       isolate_group_->ForEachIsolate( | 
 |           [&](Isolate* isolate) { | 
 |             for (intptr_t i = 0; i < isolate->NumServiceIdZones(); ++i) { | 
 |               isolate->GetServiceIdZone(i)->VisitPointers(visitor); | 
 |             } | 
 |           }, | 
 |           /*at_safepoint=*/true); | 
 |     } | 
 | #endif  // !PRODUCT | 
 |  | 
 |     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)) { | 
 |       intptr_t free = ForwardAndSweepNewPage(visitor, page); | 
 |       state_->AddNewFreeSize(free); | 
 |     } | 
 |   } | 
 |  | 
 |   DART_NOINLINE | 
 |   intptr_t 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; | 
 |     intptr_t free = 0; | 
 |     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(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 | 
 |         FreeListElement::AsElementNew(current, obj_size); | 
 |         free += obj_size; | 
 |       } | 
 |       current += obj_size; | 
 |     } | 
 |     return free; | 
 |   } | 
 |  | 
 |  private: | 
 |   PageSpace* old_space_; | 
 |   FreeList* freelist_; | 
 |   EpilogueState* state_; | 
 | }; | 
 |  | 
 | void GCIncrementalCompactor::Evacuate(PageSpace* old_space) { | 
 |   IsolateGroup* isolate_group = IsolateGroup::Current(); | 
 |   isolate_group->ReleaseStoreBuffers(); | 
 |   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, old_space, | 
 |                                   old_space->DataFreeList(i), &state)); | 
 |   } | 
 |   isolate_group->safepoint_handler()->RunTasks(&tasks); | 
 |  | 
 |   old_space->heap_->new_space()->set_freed_in_words(state.NewFreeSize() >> | 
 |                                                     kWordSizeLog2); | 
 | } | 
 |  | 
 | 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 |