// Copyright (c) 2011, 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/marker.h"

#include "platform/assert.h"
#include "platform/atomic.h"
#include "platform/thread_sanitizer.h"
#include "vm/allocation.h"
#include "vm/dart_api_state.h"
#include "vm/heap/gc_shared.h"
#include "vm/heap/pages.h"
#include "vm/heap/pointer_block.h"
#include "vm/isolate.h"
#include "vm/log.h"
#include "vm/object_id_ring.h"
#include "vm/raw_object.h"
#include "vm/stack_frame.h"
#include "vm/tagged_pointer.h"
#include "vm/thread_barrier.h"
#include "vm/thread_pool.h"
#include "vm/timeline.h"
#include "vm/visitor.h"

namespace dart {

class MarkingVisitor : public ObjectPointerVisitor {
 public:
  MarkingVisitor(IsolateGroup* isolate_group,
                 PageSpace* page_space,
                 MarkingStack* old_marking_stack,
                 MarkingStack* new_marking_stack,
                 MarkingStack* tlab_deferred_marking_stack,
                 MarkingStack* deferred_marking_stack)
      : ObjectPointerVisitor(isolate_group),
        page_space_(page_space),
        old_work_list_(old_marking_stack),
        new_work_list_(new_marking_stack),
        tlab_deferred_work_list_(tlab_deferred_marking_stack),
        deferred_work_list_(deferred_marking_stack),
        marked_bytes_(0),
        marked_micros_(0),
        concurrent_(true),
        has_evacuation_candidate_(false) {}
  ~MarkingVisitor() { ASSERT(delayed_.IsEmpty()); }

  uintptr_t marked_bytes() const { return marked_bytes_; }
  int64_t marked_micros() const { return marked_micros_; }
  void AddMicros(int64_t micros) { marked_micros_ += micros; }
  void set_concurrent(bool value) { concurrent_ = value; }

#ifdef DEBUG
  constexpr static const char* const kName = "Marker";
#endif

  static bool IsMarked(ObjectPtr raw) {
    ASSERT(raw->IsHeapObject());
    return raw->untag()->IsMarked();
  }

  void FinishedRoots() {
    // Nothing to remember for roots. Don't carry over to objects.
    has_evacuation_candidate_ = false;
  }

  bool ProcessPendingWeakProperties() {
    bool more_to_mark = false;
    WeakPropertyPtr cur_weak = delayed_.weak_properties.Release();
    while (cur_weak != WeakProperty::null()) {
      WeakPropertyPtr next_weak =
          cur_weak->untag()->next_seen_by_gc_.Decompress(cur_weak->heap_base());
      ObjectPtr raw_key = cur_weak->untag()->key();
      // Reset the next pointer in the weak property.
      cur_weak->untag()->next_seen_by_gc_ = WeakProperty::null();
      if (raw_key->IsImmediateObject() || raw_key->untag()->IsMarked()) {
        ObjectPtr raw_val = cur_weak->untag()->value_ignore_race();
        if (!raw_val->IsImmediateObject() && !raw_val->untag()->IsMarked()) {
          more_to_mark = true;
        }

        // The key is marked so we make sure to properly visit all pointers
        // originating from this weak property.
        cur_weak->untag()->VisitPointersNonvirtual(this);
        if (has_evacuation_candidate_) {
          has_evacuation_candidate_ = false;
          if (!cur_weak->untag()->IsCardRemembered()) {
            if (cur_weak->untag()->TryAcquireRememberedBit()) {
              Thread::Current()->StoreBufferAddObjectGC(cur_weak);
            }
          }
        }

      } else {
        // Requeue this weak property to be handled later.
        ASSERT(IsMarked(cur_weak));
        delayed_.weak_properties.Enqueue(cur_weak);
      }
      // Advance to next weak property in the queue.
      cur_weak = next_weak;
    }
    return more_to_mark;
  }

  DART_NOINLINE
  void YieldConcurrentMarking() {
    old_work_list_.Flush();
    new_work_list_.Flush();
    tlab_deferred_work_list_.Flush();
    deferred_work_list_.Flush();
    Thread* thread = Thread::Current();
    thread->StoreBufferReleaseGC();
    page_space_->YieldConcurrentMarking();
    thread->StoreBufferAcquireGC();
  }

  static bool InTLAB(ObjectPtr obj) {
    ASSERT(obj->IsNewObject());
    Page* page = Page::Of(obj);
    // This load-acquire pairs with the store-release in Page::Release. If we
    // see an object below Page::top_, we'll see at least its initializing
    // stores. Page::top_ is rarely updated when a thread starts or finishes
    // using a TLAB. Importantly, here we are not reading Thread::top_, that is
    // updated frequently as a store-relaxed as part of bump-pointer allocation.
    return page->original_top() <= static_cast<uword>(obj);
  }

  void DrainMarkingStackWithPauseChecks() {
    ASSERT(concurrent_);
    Thread* thread = Thread::Current();
    do {
      ObjectPtr obj;
      while (MarkerWorkList::Pop(&old_work_list_, &new_work_list_, &obj)) {
        ASSERT(!has_evacuation_candidate_);

        if (obj->IsNewObject()) {
          if (InTLAB(obj)) {
            // New-space objects still in a TLAB are deferred. This allows the
            // compiler to remove write barriers for freshly allocated objects.
            tlab_deferred_work_list_.Push(obj);
            if (page_space_->pause_concurrent_marking()) [[unlikely]] {
              YieldConcurrentMarking();
            }
            continue;
          }
        }

        const intptr_t class_id = obj->GetClassIdOfHeapObject();
        ASSERT(class_id != kIllegalCid);
        ASSERT(class_id != kFreeListElement);
        ASSERT(class_id != kForwardingCorpse);

        intptr_t size;
        if (class_id == kWeakPropertyCid) {
          size = ProcessWeakProperty(static_cast<WeakPropertyPtr>(obj));
        } else if (class_id == kWeakReferenceCid) {
          size = ProcessWeakReference(static_cast<WeakReferencePtr>(obj));
        } else if (class_id == kWeakArrayCid) {
          size = ProcessWeakArray(static_cast<WeakArrayPtr>(obj));
        } else if (class_id == kFinalizerEntryCid) {
          size = ProcessFinalizerEntry(static_cast<FinalizerEntryPtr>(obj));
        } else if (class_id == kSuspendStateCid) {
          // Shape changing is not compatible with concurrent marking.
          deferred_work_list_.Push(obj);
          size = obj->untag()->HeapSize();
        } else if (obj->untag()->IsCardRemembered()) {
          ASSERT((class_id == kArrayCid) || (class_id == kImmutableArrayCid));
          size = VisitCards(static_cast<ArrayPtr>(obj));
        } else {
          size = obj->untag()->VisitPointersNonvirtual(this);
        }
        if (has_evacuation_candidate_) {
          has_evacuation_candidate_ = false;
          if (!obj->untag()->IsCardRemembered()) {
            if (obj->untag()->TryAcquireRememberedBit()) {
              thread->StoreBufferAddObjectGC(obj);
            }
          }
        }
        if (!obj->IsNewObject()) {
          marked_bytes_ += size;
        }

        if (page_space_->pause_concurrent_marking()) [[unlikely]] {
          YieldConcurrentMarking();
        }
      }
    } while (ProcessPendingWeakProperties());

    ASSERT(old_work_list_.IsLocalEmpty());
    // In case of scavenge before final marking.
    new_work_list_.Flush();
    tlab_deferred_work_list_.Flush();
    deferred_work_list_.Flush();
  }

  intptr_t VisitCards(ArrayPtr obj) {
    ASSERT(obj->IsArray() || obj->IsImmutableArray());
    ASSERT(obj->untag()->IsCardRemembered());
    CompressedObjectPtr* obj_from = obj->untag()->from();
    CompressedObjectPtr* obj_to =
        obj->untag()->to(Smi::Value(obj->untag()->length()));
    uword heap_base = obj.heap_base();

    Page* page = Page::Of(obj);
    for (intptr_t i = 0, n = page->card_table_size(); i < n; i++) {
      CompressedObjectPtr* card_from =
          reinterpret_cast<CompressedObjectPtr*>(page) +
          (i << Page::kSlotsPerCardLog2);
      CompressedObjectPtr* card_to =
          reinterpret_cast<CompressedObjectPtr*>(card_from) +
          (1 << Page::kSlotsPerCardLog2) - 1;
      // Minus 1 because to is inclusive.

      if (card_from < obj_from) {
        // First card overlaps with header.
        card_from = obj_from;
      }
      if (card_to > obj_to) {
        // Last card(s) may extend past the object. Array truncation can make
        // this happen for more than one card.
        card_to = obj_to;
      }

      VisitCompressedPointers(heap_base, card_from, card_to);
      if (has_evacuation_candidate_) {
        has_evacuation_candidate_ = false;
        page->RememberCard(card_from);
      }

      if (((i + 1) % kCardsPerInterruptCheck) == 0) {
        if (page_space_->pause_concurrent_marking()) [[unlikely]] {
          YieldConcurrentMarking();
        }
      }
    }

    return obj->untag()->HeapSize();
  }

  void DrainMarkingStack() {
    ASSERT(!concurrent_);
    Thread* thread = Thread::Current();
    do {
      ObjectPtr obj;
      while (MarkerWorkList::Pop(&old_work_list_, &new_work_list_, &obj)) {
        ASSERT(!has_evacuation_candidate_);

        const intptr_t class_id = obj->GetClassIdOfHeapObject();
        ASSERT(class_id != kIllegalCid);
        ASSERT(class_id != kFreeListElement);
        ASSERT(class_id != kForwardingCorpse);

        intptr_t size;
        if (class_id == kWeakPropertyCid) {
          size = ProcessWeakProperty(static_cast<WeakPropertyPtr>(obj));
        } else if (class_id == kWeakReferenceCid) {
          size = ProcessWeakReference(static_cast<WeakReferencePtr>(obj));
        } else if (class_id == kWeakArrayCid) {
          size = ProcessWeakArray(static_cast<WeakArrayPtr>(obj));
        } else if (class_id == kFinalizerEntryCid) {
          size = ProcessFinalizerEntry(static_cast<FinalizerEntryPtr>(obj));
        } else {
          if (obj->untag()->IsCardRemembered()) {
            ASSERT((class_id == kArrayCid) || (class_id == kImmutableArrayCid));
            size = VisitCards(static_cast<ArrayPtr>(obj));
          } else {
            size = obj->untag()->VisitPointersNonvirtual(this);
          }
        }
        if (has_evacuation_candidate_) {
          has_evacuation_candidate_ = false;
          if (!obj->untag()->IsCardRemembered() &&
              obj->untag()->TryAcquireRememberedBit()) {
            thread->StoreBufferAddObjectGC(obj);
          }
        }
        if (!obj->IsNewObject()) {
          marked_bytes_ += size;
        }
      }
    } while (ProcessPendingWeakProperties());
  }

  void ProcessOldMarkingStackUntil(int64_t deadline) {
    // We check the clock *before* starting a batch of work, but we want to
    // *end* work before the deadline. So we compare to the deadline adjusted
    // by a conservative estimate of the duration of one batch of work.
    deadline -= 1500;

    // A 512kB budget is chosen to be large enough that we don't waste too much
    // time on the overhead of exiting ProcessMarkingStack, querying the clock,
    // and re-entering, and small enough that a few batches can fit in the idle
    // time between animation frames. This amount of marking takes ~1ms on a
    // Pixel phone.
    constexpr intptr_t kBudget = 512 * KB;

    while ((OS::GetCurrentMonotonicMicros() < deadline) &&
           ProcessOldMarkingStack(kBudget)) {
    }
  }

  bool ProcessOldMarkingStack(intptr_t remaining_budget) {
    Thread* thread = Thread::Current();
    do {
      // First drain the marking stacks.
      ObjectPtr obj;
      while (old_work_list_.Pop(&obj)) {
        ASSERT(!has_evacuation_candidate_);

        const intptr_t class_id = obj->GetClassIdOfHeapObject();
        ASSERT(class_id != kIllegalCid);
        ASSERT(class_id != kFreeListElement);
        ASSERT(class_id != kForwardingCorpse);

        intptr_t size;
        if (class_id == kWeakPropertyCid) {
          size = ProcessWeakProperty(static_cast<WeakPropertyPtr>(obj));
        } else if (class_id == kWeakReferenceCid) {
          size = ProcessWeakReference(static_cast<WeakReferencePtr>(obj));
        } else if (class_id == kWeakArrayCid) {
          size = ProcessWeakArray(static_cast<WeakArrayPtr>(obj));
        } else if (class_id == kFinalizerEntryCid) {
          size = ProcessFinalizerEntry(static_cast<FinalizerEntryPtr>(obj));
        } else if (concurrent_ && class_id == kSuspendStateCid) {
          // Shape changing is not compatible with concurrent marking.
          deferred_work_list_.Push(obj);
          size = obj->untag()->HeapSize();
        } else {
          if ((class_id == kArrayCid) || (class_id == kImmutableArrayCid)) {
            size = obj->untag()->HeapSize();
            if (size > remaining_budget) {
              old_work_list_.Push(obj);
              return true;  // More to mark.
            }
          }
          if (obj->untag()->IsCardRemembered()) {
            ASSERT((class_id == kArrayCid) || (class_id == kImmutableArrayCid));
            size = VisitCards(static_cast<ArrayPtr>(obj));
          } else {
            size = obj->untag()->VisitPointersNonvirtual(this);
          }
        }
        if (has_evacuation_candidate_) {
          has_evacuation_candidate_ = false;
          if (!obj->untag()->IsCardRemembered() &&
              obj->untag()->TryAcquireRememberedBit()) {
            thread->StoreBufferAddObjectGC(obj);
          }
        }
        marked_bytes_ += size;
        remaining_budget -= size;
        if (remaining_budget < 0) {
          return true;  // More to mark.
        }
      }
      // Marking stack is empty.
    } while (ProcessPendingWeakProperties());

    return false;  // No more work.
  }

  // Races: The concurrent marker is racing with the mutator, but this race is
  // harmless. The concurrent marker will only visit objects that were created
  // before the marker started. It will ignore all new-space objects based on
  // pointer alignment, and it will ignore old-space objects created after the
  // marker started because old-space objects allocated while marking is in
  // progress are allocated black (mark bit set). When visiting object slots,
  // the marker can see either the value it had when marking started (because
  // spawning the marker task creates acq-rel ordering) or any value later
  // stored into that slot. Because pointer slots always contain pointers (i.e.,
  // we don't do any in-place unboxing like V8), any value we read from the slot
  // is safe.
  NO_SANITIZE_THREAD
  ObjectPtr LoadPointerIgnoreRace(ObjectPtr* ptr) { return *ptr; }
  NO_SANITIZE_THREAD
  CompressedObjectPtr LoadCompressedPointerIgnoreRace(
      CompressedObjectPtr* ptr) {
    return *ptr;
  }

  void VisitPointers(ObjectPtr* first, ObjectPtr* last) override {
    bool has_evacuation_candidate = false;
    for (ObjectPtr* current = first; current <= last; current++) {
      has_evacuation_candidate |= MarkObject(LoadPointerIgnoreRace(current));
    }
    has_evacuation_candidate_ |= has_evacuation_candidate;
  }

#if defined(DART_COMPRESSED_POINTERS)
  void VisitCompressedPointers(uword heap_base,
                               CompressedObjectPtr* first,
                               CompressedObjectPtr* last) override {
    bool has_evacuation_candidate = false;
    for (CompressedObjectPtr* current = first; current <= last; current++) {
      has_evacuation_candidate |= MarkObject(
          LoadCompressedPointerIgnoreRace(current).Decompress(heap_base));
    }
    has_evacuation_candidate_ |= has_evacuation_candidate;
  }
#endif

  intptr_t ProcessWeakProperty(WeakPropertyPtr raw_weak) {
    // The fate of the weak property is determined by its key.
    ObjectPtr raw_key =
        LoadCompressedPointerIgnoreRace(&raw_weak->untag()->key_)
            .Decompress(raw_weak->heap_base());
    if (raw_key->IsHeapObject() && !raw_key->untag()->IsMarked()) {
      // Key was white. Enqueue the weak property.
      ASSERT(IsMarked(raw_weak));
      delayed_.weak_properties.Enqueue(raw_weak);
      return raw_weak->untag()->HeapSize();
    }
    // Key is gray or black. Make the weak property black.
    return raw_weak->untag()->VisitPointersNonvirtual(this);
  }

  intptr_t ProcessWeakReference(WeakReferencePtr raw_weak) {
    // The fate of the target field is determined by the target.
    // The type arguments always stay alive.
    ObjectPtr raw_target =
        LoadCompressedPointerIgnoreRace(&raw_weak->untag()->target_)
            .Decompress(raw_weak->heap_base());
    if (raw_target->IsHeapObject()) {
      if (!raw_target->untag()->IsMarked()) {
        // Target was white. Enqueue the weak reference. It is potentially dead.
        // It might still be made alive by weak properties in next rounds.
        ASSERT(IsMarked(raw_weak));
        delayed_.weak_references.Enqueue(raw_weak);
      } else {
        if (raw_target->untag()->IsEvacuationCandidate()) {
          has_evacuation_candidate_ = true;
        }
      }
    }
    // Always visit the type argument.
    ObjectPtr raw_type_arguments =
        LoadCompressedPointerIgnoreRace(&raw_weak->untag()->type_arguments_)
            .Decompress(raw_weak->heap_base());
    if (MarkObject(raw_type_arguments)) {
      has_evacuation_candidate_ = true;
    }
    return raw_weak->untag()->HeapSize();
  }

  intptr_t ProcessWeakArray(WeakArrayPtr raw_weak) {
    delayed_.weak_arrays.Enqueue(raw_weak);
    return raw_weak->untag()->HeapSize();
  }

  intptr_t ProcessFinalizerEntry(FinalizerEntryPtr raw_entry) {
    ASSERT(IsMarked(raw_entry));
    delayed_.finalizer_entries.Enqueue(raw_entry);
    // Only visit token and next.
    if (MarkObject(LoadCompressedPointerIgnoreRace(&raw_entry->untag()->token_)
                       .Decompress(raw_entry->heap_base()))) {
      has_evacuation_candidate_ = true;
    }
    if (MarkObject(LoadCompressedPointerIgnoreRace(&raw_entry->untag()->next_)
                       .Decompress(raw_entry->heap_base()))) {
      has_evacuation_candidate_ = true;
    }
    return raw_entry->untag()->HeapSize();
  }

  void ProcessDeferredMarking() {
    Thread* thread = Thread::Current();
    TIMELINE_FUNCTION_GC_DURATION(thread, "ProcessDeferredMarking");

    ObjectPtr obj;
    while (deferred_work_list_.Pop(&obj)) {
      ASSERT(!has_evacuation_candidate_);
      ASSERT(obj->IsHeapObject());
      // We need to scan objects even if they were already scanned via ordinary
      // marking. An object may have changed since its ordinary scan and been
      // added to deferred marking stack to compensate for write-barrier
      // elimination.
      // A given object may be included in the deferred marking stack multiple
      // times. It may or may not also be in the ordinary marking stack, so
      // failing to acquire the mark bit here doesn't reliably indicate the
      // object was already encountered through the deferred marking stack. Our
      // processing here is idempotent, so repeated visits only hurt performance
      // but not correctness. Duplication is expected to be low.
      // By the absence of a special case, we are treating WeakProperties as
      // strong references here. This guarantees a WeakProperty will only be
      // added to the delayed_weak_properties_ list of the worker that
      // encounters it during ordinary marking. This is in the same spirit as
      // the eliminated write barrier, which would have added the newly written
      // key and value to the ordinary marking stack.
      intptr_t size = obj->untag()->VisitPointersNonvirtual(this);
      // Add the size only if we win the marking race to prevent
      // double-counting.
      if (obj->untag()->TryAcquireMarkBit()) {
        if (!obj->IsNewObject()) {
          marked_bytes_ += size;
        }
      }
      if (has_evacuation_candidate_) {
        has_evacuation_candidate_ = false;
        if (!obj->untag()->IsCardRemembered() &&
            obj->untag()->TryAcquireRememberedBit()) {
          thread->StoreBufferAddObjectGC(obj);
        }
      }
    }
  }

  // Called when all marking is complete. Any attempt to push to the mark stack
  // after this will trigger an error.
  void FinalizeMarking() {
    old_work_list_.Finalize();
    new_work_list_.Finalize();
    tlab_deferred_work_list_.Finalize();
    deferred_work_list_.Finalize();
    MournFinalizerEntries();
    // MournFinalizerEntries inserts newly discovered dead entries into the
    // linked list attached to the Finalizer. This might create
    // cross-generational references which might be added to the store
    // buffer. Release the store buffer to satisfy the invariant that
    // thread local store buffer is empty after marking and all references
    // are processed.
    Thread::Current()->ReleaseStoreBuffer();
  }

  void MournWeakProperties() {
    WeakPropertyPtr current = delayed_.weak_properties.Release();
    while (current != WeakProperty::null()) {
      WeakPropertyPtr next = current->untag()->next_seen_by_gc();
      current->untag()->next_seen_by_gc_ = WeakProperty::null();
      current->untag()->key_ = Object::null();
      current->untag()->value_ = Object::null();
      current = next;
    }
  }

  void MournWeakReferences() {
    WeakReferencePtr current = delayed_.weak_references.Release();
    while (current != WeakReference::null()) {
      WeakReferencePtr next = current->untag()->next_seen_by_gc();
      current->untag()->next_seen_by_gc_ = WeakReference::null();
      ForwardOrSetNullIfCollected(current, &current->untag()->target_);
      current = next;
    }
  }

  void MournWeakArrays() {
    WeakArrayPtr current = delayed_.weak_arrays.Release();
    while (current != WeakArray::null()) {
      WeakArrayPtr next = current->untag()->next_seen_by_gc();
      current->untag()->next_seen_by_gc_ = WeakArray::null();
      intptr_t length = Smi::Value(current->untag()->length());
      for (intptr_t i = 0; i < length; i++) {
        ForwardOrSetNullIfCollected(current, &current->untag()->data()[i]);
      }
      current = next;
    }
  }

  void MournFinalizerEntries() {
    FinalizerEntryPtr current = delayed_.finalizer_entries.Release();
    while (current != FinalizerEntry::null()) {
      FinalizerEntryPtr next = current->untag()->next_seen_by_gc();
      current->untag()->next_seen_by_gc_ = FinalizerEntry::null();
      MournFinalizerEntry(this, current);
      current = next;
    }
  }

  // Returns whether the object referred to in `slot` was GCed this GC.
  static bool ForwardOrSetNullIfCollected(ObjectPtr parent,
                                          CompressedObjectPtr* slot) {
    ObjectPtr target = slot->Decompress(parent->heap_base());
    if (target->IsImmediateObject()) {
      // Object not touched during this GC.
      return false;
    }
    if (target->untag()->IsMarked()) {
      // Object already null (which is permanently marked) or has survived this
      // GC.
      if (target->untag()->IsEvacuationCandidate()) {
        if (parent->untag()->IsCardRemembered()) {
          Page::Of(parent)->RememberCard(slot);
        } else {
          if (parent->untag()->TryAcquireRememberedBit()) {
            Thread::Current()->StoreBufferAddObjectGC(parent);
          }
        }
      }
      return false;
    }
    *slot = Object::null();
    return true;
  }

  bool WaitForWork(RelaxedAtomic<uintptr_t>* num_busy) {
    return old_work_list_.WaitForWork(num_busy);
  }

  void Flush(GCLinkedLists* global_list) {
    old_work_list_.Flush();
    new_work_list_.Flush();
    tlab_deferred_work_list_.Flush();
    deferred_work_list_.Flush();
    delayed_.FlushInto(global_list);
  }

  void Adopt(GCLinkedLists* other) {
    ASSERT(delayed_.IsEmpty());
    other->FlushInto(&delayed_);
  }

  void AbandonWork() {
    old_work_list_.AbandonWork();
    new_work_list_.AbandonWork();
    tlab_deferred_work_list_.AbandonWork();
    deferred_work_list_.AbandonWork();
    delayed_.Release();
  }

  void FinalizeIncremental(GCLinkedLists* global_list) {
    old_work_list_.Flush();
    old_work_list_.Finalize();
    new_work_list_.Flush();
    new_work_list_.Finalize();
    tlab_deferred_work_list_.Flush();
    tlab_deferred_work_list_.Finalize();
    deferred_work_list_.Flush();
    deferred_work_list_.Finalize();
    delayed_.FlushInto(global_list);
  }

  GCLinkedLists* delayed() { return &delayed_; }

 private:
  DART_FORCE_INLINE
  bool MarkObject(ObjectPtr obj) {
    if (obj->IsImmediateObject()) {
      return false;
    }

    if (obj->IsNewObject()) {
#if defined(HOST_HAS_FAST_WRITE_WRITE_FENCE)
      // Ignore race: TSAN doesn't support fences.
      if (obj->untag()->TryAcquireMarkBitIgnoreRace()) {
        new_work_list_.Push(obj);
      }
#else
      if (concurrent_ && InTLAB(obj)) {
        // New-space objects still in a TLAB might race with the header's
        // initializing store.
        deferred_work_list_.Push(obj);
      } else if (obj->untag()->TryAcquireMarkBit()) {
        new_work_list_.Push(obj);
      }
#endif
      return false;
    }

    // While it might seem this is redundant with TryAcquireMarkBit, we must
    // do this check first to avoid attempting an atomic::fetch_and on the
    // read-only vm-isolate or image pages, which can fault even if there is no
    // change in the value.
    // Doing this before checking for an Instructions object avoids
    // unnecessary queueing of pre-marked objects.
    // Race: The concurrent marker may observe a pointer into a heap page that
    // was allocated after the concurrent marker started. It can read either a
    // zero or the header of an object allocated black, both of which appear
    // marked.
    // No ASAN/MSAN: expensive without offering much insight for GC bugs.
    uword tags = obj->untag()->tags_no_sanitize();
    if (UntaggedObject::IsMarked(tags)) {
      return UntaggedObject::IsEvacuationCandidate(tags);
    }

    intptr_t class_id = UntaggedObject::ClassIdTag::decode(tags);
    ASSERT(class_id != kFreeListElement);

    if (class_id == kInstructionsCid) [[unlikely]] {
      // If this is the concurrent marker, this object may be non-writable due
      // to W^X (--write-protect-code).
      deferred_work_list_.Push(obj);
      return false;
    }

    if (obj->untag()->TryAcquireMarkBit()) {
      old_work_list_.Push(obj);
    }

    return UntaggedObject::IsEvacuationCandidate(tags);
  }

  PageSpace* page_space_;
  MarkerWorkList old_work_list_;
  MarkerWorkList new_work_list_;
  MarkerWorkList tlab_deferred_work_list_;
  MarkerWorkList deferred_work_list_;
  GCLinkedLists delayed_;
  uintptr_t marked_bytes_;
  int64_t marked_micros_;
  bool concurrent_;
  bool has_evacuation_candidate_;

  DISALLOW_IMPLICIT_CONSTRUCTORS(MarkingVisitor);
};

static bool IsUnreachable(const ObjectPtr obj) {
  if (obj->IsImmediateObject()) {
    return false;
  }
  return !obj->untag()->IsMarked();
}

class MarkingWeakVisitor : public HandleVisitor {
 public:
  explicit MarkingWeakVisitor(IsolateGroup* isolate_group)
      : HandleVisitor(), isolate_group_(isolate_group) {}

  void VisitHandle(uword addr) override {
    FinalizablePersistentHandle* handle =
        reinterpret_cast<FinalizablePersistentHandle*>(addr);
    ObjectPtr obj = handle->ptr();
    if (IsUnreachable(obj)) {
      handle->UpdateUnreachable(isolate_group_);
    }
  }

 private:
  IsolateGroup* isolate_group_;

  DISALLOW_COPY_AND_ASSIGN(MarkingWeakVisitor);
};

void GCMarker::Prologue() {
  isolate_group_->ReleaseStoreBuffers();
  new_marking_stack_.PushAll(tlab_deferred_marking_stack_.PopAll());

#if defined(DART_DYNAMIC_MODULES)
  isolate_group_->ForEachIsolate(
      [&](Isolate* isolate) {
        Thread* mutator_thread = isolate->mutator_thread();
        if (mutator_thread != nullptr) {
          Interpreter* interpreter = mutator_thread->interpreter();
          if (interpreter != nullptr) {
            interpreter->ClearLookupCache();
          }
        }
      },
      /*at_safepoint=*/true);
#endif  // defined(DART_DYNAMIC_MODULES)
}

void GCMarker::Epilogue() {}

enum RootSlices {
  kIsolate = 0,
  kNumFixedRootSlices = 1,
};

void GCMarker::ResetSlices() {
  ASSERT(Thread::Current()->OwnsGCSafepoint());

  root_slices_started_ = 0;
  root_slices_finished_ = 0;
  root_slices_count_ = kNumFixedRootSlices;

  weak_slices_started_ = 0;
}

void GCMarker::IterateRoots(ObjectPointerVisitor* visitor) {
  for (;;) {
    intptr_t slice = root_slices_started_.fetch_add(1);
    if (slice >= root_slices_count_) {
      break;  // No more slices.
    }

    switch (slice) {
      case kIsolate: {
        // TODO(gc): Split this by isolate?
        TIMELINE_FUNCTION_GC_DURATION(Thread::Current(),
                                      "ProcessIsolateGroupRoots");
        isolate_group_->VisitObjectPointers(
            visitor, ValidationPolicy::kDontValidateFrames);
        break;
      }
    }

    MonitorLocker ml(&root_slices_monitor_);
    root_slices_finished_++;
    if (root_slices_finished_ == root_slices_count_) {
      ml.Notify();
    }
  }
}

enum WeakSlices {
  kWeakHandles = 0,
  kWeakTables,
  kRememberedSet,
  kNumWeakSlices,
};

void GCMarker::IterateWeakRoots(Thread* thread) {
  for (;;) {
    intptr_t slice = weak_slices_started_.fetch_add(1);
    if (slice >= kNumWeakSlices) {
      return;  // No more slices.
    }

    switch (slice) {
      case kWeakHandles:
        ProcessWeakHandles(thread);
        break;
      case kWeakTables:
        ProcessWeakTables(thread);
        break;
      case kRememberedSet:
        ProcessRememberedSet(thread);
        break;
      default:
        UNREACHABLE();
    }
  }
}

void GCMarker::ProcessWeakHandles(Thread* thread) {
  TIMELINE_FUNCTION_GC_DURATION(thread, "ProcessWeakHandles");
  MarkingWeakVisitor visitor(isolate_group_);
  ApiState* state = isolate_group_->api_state();
  ASSERT(state != nullptr);
  isolate_group_->VisitWeakPersistentHandles(&visitor);
}

void GCMarker::ProcessWeakTables(Thread* thread) {
  TIMELINE_FUNCTION_GC_DURATION(thread, "ProcessWeakTables");
  for (int sel = 0; sel < Heap::kNumWeakSelectors; sel++) {
    Dart_HeapSamplingDeleteCallback cleanup = nullptr;
#if !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER)
    if (sel == Heap::kHeapSamplingData) {
      cleanup = HeapProfileSampler::delete_callback();
    }
#endif
    WeakTable* table =
        heap_->GetWeakTable(Heap::kOld, static_cast<Heap::WeakSelector>(sel));
    intptr_t size = table->size();
    for (intptr_t i = 0; i < size; i++) {
      if (table->IsValidEntryAtExclusive(i)) {
        // The object has been collected.
        ObjectPtr obj = table->ObjectAtExclusive(i);
        if (obj->IsHeapObject() && !obj->untag()->IsMarked()) {
          if (cleanup != nullptr) {
            cleanup(reinterpret_cast<void*>(table->ValueAtExclusive(i)));
          }
          table->InvalidateAtExclusive(i);
        }
      }
    }
    table =
        heap_->GetWeakTable(Heap::kNew, static_cast<Heap::WeakSelector>(sel));
    size = table->size();
    for (intptr_t i = 0; i < size; i++) {
      if (table->IsValidEntryAtExclusive(i)) {
        // The object has been collected.
        ObjectPtr obj = table->ObjectAtExclusive(i);
        if (obj->IsHeapObject() && !obj->untag()->IsMarked()) {
          if (cleanup != nullptr) {
            cleanup(reinterpret_cast<void*>(table->ValueAtExclusive(i)));
          }
          table->InvalidateAtExclusive(i);
        }
      }
    }
  }
}

void GCMarker::ProcessRememberedSet(Thread* thread) {
  TIMELINE_FUNCTION_GC_DURATION(thread, "ProcessRememberedSet");
  // Filter collected objects from the remembered set.
  StoreBuffer* store_buffer = isolate_group_->store_buffer();
  StoreBufferBlock* reading = store_buffer->PopAll();
  StoreBufferBlock* writing = store_buffer->PopNonFullBlock();
  while (reading != nullptr) {
    StoreBufferBlock* next = reading->next();
    // Generated code appends to store buffers; tell MemorySanitizer.
    MSAN_UNPOISON(reading, sizeof(*reading));
    while (!reading->IsEmpty()) {
      ObjectPtr obj = reading->Pop();
      ASSERT(!obj->IsForwardingCorpse());
      ASSERT(obj->untag()->IsRemembered());
      if (obj->untag()->IsMarked()) {
        writing->Push(obj);
        if (writing->IsFull()) {
          store_buffer->PushBlock(writing, StoreBuffer::kIgnoreThreshold);
          writing = store_buffer->PopNonFullBlock();
        }
      }
    }
    reading->Reset();
    // Return the emptied block for recycling (no need to check threshold).
    store_buffer->PushBlock(reading, StoreBuffer::kIgnoreThreshold);
    reading = next;
  }
  store_buffer->PushBlock(writing, StoreBuffer::kIgnoreThreshold);
}

class ParallelMarkTask : public SafepointTask {
 public:
  ParallelMarkTask(GCMarker* marker,
                   IsolateGroup* isolate_group,
                   MarkingStack* marking_stack,
                   ThreadBarrier* barrier,
                   MarkingVisitor* visitor,
                   RelaxedAtomic<uintptr_t>* num_busy)
      : SafepointTask(isolate_group, barrier, Thread::kMarkerTask),
        marker_(marker),
        marking_stack_(marking_stack),
        visitor_(visitor),
        num_busy_(num_busy) {}

  void RunEnteredIsolateGroup() override {
    {
      Thread* thread = Thread::Current();
      TIMELINE_FUNCTION_GC_DURATION(thread, "ParallelMark");
      int64_t start = OS::GetCurrentMonotonicMicros();

      // Phase 1: Iterate over roots and drain marking stack in tasks.
      num_busy_->fetch_add(1u);
      visitor_->set_concurrent(false);
      marker_->IterateRoots(visitor_);
      visitor_->FinishedRoots();

      visitor_->ProcessDeferredMarking();

      bool more_to_mark = false;
      do {
        do {
          visitor_->DrainMarkingStack();
        } while (visitor_->WaitForWork(num_busy_));
        // Wait for all markers to stop.
        barrier_->Sync();
#if defined(DEBUG)
        ASSERT(num_busy_->load() == 0);
        // Caveat: must not allow any marker to continue past the barrier
        // before we checked num_busy, otherwise one of them might rush
        // ahead and increment it.
        barrier_->Sync();
#endif
        // Check if we have any pending properties with marked keys.
        // Those might have been marked by another marker.
        more_to_mark = visitor_->ProcessPendingWeakProperties();
        if (more_to_mark) {
          // We have more work to do. Notify others.
          num_busy_->fetch_add(1u);
        }

        // Wait for all other markers to finish processing their pending
        // weak properties and decide if they need to continue marking.
        // Caveat: we need two barriers here to make this decision in lock step
        // between all markers and the main thread.
        barrier_->Sync();
        if (!more_to_mark && (num_busy_->load() > 0)) {
          // All markers continue to mark as long as any single marker has
          // some work to do.
          num_busy_->fetch_add(1u);
          more_to_mark = true;
        }
        barrier_->Sync();
      } while (more_to_mark);

      // Phase 2: deferred marking.
      visitor_->ProcessDeferredMarking();
      barrier_->Sync();

      // Phase 3: Weak processing and statistics.
      visitor_->MournWeakProperties();
      visitor_->MournWeakReferences();
      visitor_->MournWeakArrays();
      // Don't MournFinalizerEntries here, do it on main thread, so that we
      // don't have to coordinate workers.

      thread->ReleaseStoreBuffer();  // Ahead of IterateWeak
      barrier_->Sync();
      marker_->IterateWeakRoots(thread);
      int64_t stop = OS::GetCurrentMonotonicMicros();
      visitor_->AddMicros(stop - start);
      if (FLAG_log_marker_tasks) {
        THR_Print("Task marked %" Pd " bytes in %" Pd64 " micros.\n",
                  visitor_->marked_bytes(), visitor_->marked_micros());
      }
    }
  }

 private:
  GCMarker* marker_;
  MarkingStack* marking_stack_;
  MarkingVisitor* visitor_;
  RelaxedAtomic<uintptr_t>* num_busy_;

  DISALLOW_COPY_AND_ASSIGN(ParallelMarkTask);
};

class ConcurrentMarkTask : public ThreadPool::Task {
 public:
  ConcurrentMarkTask(GCMarker* marker,
                     IsolateGroup* isolate_group,
                     PageSpace* page_space,
                     MarkingVisitor* visitor)
      : marker_(marker),
        isolate_group_(isolate_group),
        page_space_(page_space),
        visitor_(visitor) {
#if defined(DEBUG)
    MonitorLocker ml(page_space_->tasks_lock());
    ASSERT(page_space_->phase() == PageSpace::kMarking);
#endif
  }

  virtual void Run() {
    Thread::EnterIsolateGroupAsHelper(isolate_group_, Thread::kMarkerTask,
                                      /*bypass_safepoint=*/true);
    {
      TIMELINE_FUNCTION_GC_DURATION(Thread::Current(), "ConcurrentMark");
      int64_t start = OS::GetCurrentMonotonicMicros();

      marker_->IterateRoots(visitor_);
      visitor_->FinishedRoots();

      visitor_->DrainMarkingStackWithPauseChecks();
      int64_t stop = OS::GetCurrentMonotonicMicros();
      visitor_->AddMicros(stop - start);
      if (FLAG_log_marker_tasks) {
        THR_Print("Task marked %" Pd " bytes in %" Pd64 " micros.\n",
                  visitor_->marked_bytes(), visitor_->marked_micros());
      }
    }

    // Exit isolate cleanly *before* notifying it, to avoid shutdown race.
    Thread::ExitIsolateGroupAsHelper(/*bypass_safepoint=*/true);
    // This marker task is done. Notify the original isolate.

    Dart_Port interrupt_port = isolate_group_->interrupt_port();

    {
      MonitorLocker ml(page_space_->tasks_lock());
      page_space_->set_tasks(page_space_->tasks() - 1);
      page_space_->set_concurrent_marker_tasks(
          page_space_->concurrent_marker_tasks() - 1);
      page_space_->set_concurrent_marker_tasks_active(
          page_space_->concurrent_marker_tasks_active() - 1);
      ASSERT(page_space_->phase() == PageSpace::kMarking);
      if (page_space_->concurrent_marker_tasks() == 0) {
        page_space_->set_phase(PageSpace::kAwaitingFinalization);
      }
      ml.NotifyAll();
    }

    PortMap::PostMessage(Message::New(
        interrupt_port, Smi::New(Thread::kVMInterrupt), Message::kOOBPriority));
  }

 private:
  GCMarker* marker_;
  IsolateGroup* isolate_group_;
  PageSpace* page_space_;
  MarkingVisitor* visitor_;

  DISALLOW_COPY_AND_ASSIGN(ConcurrentMarkTask);
};

intptr_t GCMarker::MarkedWordsPerMicro() const {
  intptr_t marked_words_per_job_micro;
  if (marked_micros_ == 0) {
    marked_words_per_job_micro = marked_words();  // Prevent division by zero.
  } else {
    marked_words_per_job_micro = marked_words() / marked_micros_;
  }
  if (marked_words_per_job_micro == 0) {
    marked_words_per_job_micro = 1;  // Prevent division by zero.
  }
  intptr_t jobs = FLAG_marker_tasks;
  if (jobs == 0) {
    jobs = 1;  // Marking on main thread is still one job.
  }
  return marked_words_per_job_micro * jobs;
}

GCMarker::GCMarker(IsolateGroup* isolate_group, Heap* heap)
    : isolate_group_(isolate_group),
      heap_(heap),
      old_marking_stack_(),
      new_marking_stack_(),
      tlab_deferred_marking_stack_(),
      deferred_marking_stack_(),
      global_list_(),
      visitors_(),
      marked_bytes_(0),
      marked_micros_(0) {
  visitors_ = new MarkingVisitor*[FLAG_marker_tasks];
  for (intptr_t i = 0; i < FLAG_marker_tasks; i++) {
    visitors_[i] = nullptr;
  }
}

GCMarker::~GCMarker() {
  // Cleanup in case isolate shutdown happens after starting the concurrent
  // marker and before finalizing.
  if (isolate_group_->old_marking_stack() != nullptr) {
    isolate_group_->DisableIncrementalBarrier();
    for (intptr_t i = 0; i < FLAG_marker_tasks; i++) {
      visitors_[i]->AbandonWork();
      delete visitors_[i];
    }
  }
  delete[] visitors_;
}

void GCMarker::StartConcurrentMark(PageSpace* page_space) {
  isolate_group_->EnableIncrementalBarrier(
      &old_marking_stack_, &new_marking_stack_, &deferred_marking_stack_);

  const intptr_t num_tasks = FLAG_marker_tasks;

  {
    // Bulk increase task count before starting any task, instead of
    // incrementing as each task is started, to prevent a task which
    // races ahead from falsely believing it was the last task to complete.
    MonitorLocker ml(page_space->tasks_lock());
    ASSERT(page_space->phase() == PageSpace::kDone);
    page_space->set_phase(PageSpace::kMarking);
    page_space->set_tasks(page_space->tasks() + num_tasks);
    page_space->set_concurrent_marker_tasks(
        page_space->concurrent_marker_tasks() + num_tasks);
    page_space->set_concurrent_marker_tasks_active(
        page_space->concurrent_marker_tasks_active() + num_tasks);
  }

  ResetSlices();
  for (intptr_t i = 0; i < num_tasks; i++) {
    ASSERT(visitors_[i] == nullptr);
    MarkingVisitor* visitor = new MarkingVisitor(
        isolate_group_, page_space, &old_marking_stack_, &new_marking_stack_,
        &tlab_deferred_marking_stack_, &deferred_marking_stack_);
    visitors_[i] = visitor;

    if (i < (num_tasks - 1)) {
      // Begin marking on a helper thread.
      bool result = Dart::thread_pool()->Run<ConcurrentMarkTask>(
          this, isolate_group_, page_space, visitor);
      ASSERT(result);
    } else {
      // For the last visitor, mark roots on the main thread.
      TIMELINE_FUNCTION_GC_DURATION(Thread::Current(), "ConcurrentMark");
      int64_t start = OS::GetCurrentMonotonicMicros();
      IterateRoots(visitor);
      visitor->FinishedRoots();
      int64_t stop = OS::GetCurrentMonotonicMicros();
      visitor->AddMicros(stop - start);
      if (FLAG_log_marker_tasks) {
        THR_Print("Task marked %" Pd " bytes in %" Pd64 " micros.\n",
                  visitor->marked_bytes(), visitor->marked_micros());
      }
      // Continue non-root marking concurrently.
      bool result = Dart::thread_pool()->Run<ConcurrentMarkTask>(
          this, isolate_group_, page_space, visitor);
      ASSERT(result);
    }
  }

  isolate_group_->DeferredMarkLiveTemporaries();

  // Wait for roots to be marked before exiting safepoint.
  MonitorLocker ml(&root_slices_monitor_);
  while (root_slices_finished_ != root_slices_count_) {
    ml.Wait();
  }
}

void GCMarker::IncrementalMarkWithUnlimitedBudget(PageSpace* page_space) {
  TIMELINE_FUNCTION_GC_DURATION(Thread::Current(),
                                "IncrementalMarkWithUnlimitedBudget");

  MarkingVisitor visitor(isolate_group_, page_space, &old_marking_stack_,
                         &new_marking_stack_, &tlab_deferred_marking_stack_,
                         &deferred_marking_stack_);
  int64_t start = OS::GetCurrentMonotonicMicros();
  visitor.ProcessOldMarkingStack(kIntptrMax);
  int64_t stop = OS::GetCurrentMonotonicMicros();
  visitor.AddMicros(stop - start);
  {
    MonitorLocker ml(page_space->tasks_lock());
    visitor.FinalizeIncremental(&global_list_);
    marked_bytes_ += visitor.marked_bytes();
    marked_micros_ += visitor.marked_micros();
  }
}

void GCMarker::IncrementalMarkWithSizeBudget(PageSpace* page_space,
                                             intptr_t size) {
  // Avoid setup overhead for tiny amounts of marking as the last bits of TLABs
  // get filled in.
  const intptr_t kMinimumMarkingStep = KB;
  if (size < kMinimumMarkingStep) return;

  TIMELINE_FUNCTION_GC_DURATION(Thread::Current(),
                                "IncrementalMarkWithSizeBudget");

  MarkingVisitor visitor(isolate_group_, page_space, &old_marking_stack_,
                         &new_marking_stack_, &tlab_deferred_marking_stack_,
                         &deferred_marking_stack_);
  int64_t start = OS::GetCurrentMonotonicMicros();
  visitor.ProcessOldMarkingStack(size);
  int64_t stop = OS::GetCurrentMonotonicMicros();
  visitor.AddMicros(stop - start);
  {
    MonitorLocker ml(page_space->tasks_lock());
    visitor.FinalizeIncremental(&global_list_);
    marked_bytes_ += visitor.marked_bytes();
    marked_micros_ += visitor.marked_micros();
  }
}

void GCMarker::IncrementalMarkWithTimeBudget(PageSpace* page_space,
                                             int64_t deadline) {
  TIMELINE_FUNCTION_GC_DURATION(Thread::Current(),
                                "IncrementalMarkWithTimeBudget");

  MarkingVisitor visitor(isolate_group_, page_space, &old_marking_stack_,
                         &new_marking_stack_, &tlab_deferred_marking_stack_,
                         &deferred_marking_stack_);
  int64_t start = OS::GetCurrentMonotonicMicros();
  visitor.ProcessOldMarkingStackUntil(deadline);
  int64_t stop = OS::GetCurrentMonotonicMicros();
  visitor.AddMicros(stop - start);
  {
    MonitorLocker ml(page_space->tasks_lock());
    visitor.FinalizeIncremental(&global_list_);
    marked_bytes_ += visitor.marked_bytes();
    marked_micros_ += visitor.marked_micros();
  }
}

class VerifyAfterMarkingVisitor : public ObjectVisitor,
                                  public ObjectPointerVisitor {
 public:
  VerifyAfterMarkingVisitor()
      : ObjectVisitor(), ObjectPointerVisitor(IsolateGroup::Current()) {}

  void VisitObject(ObjectPtr obj) override {
    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() && !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() && !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;
};

void GCMarker::MarkObjects(PageSpace* page_space) {
  if (isolate_group_->old_marking_stack() != nullptr) {
    isolate_group_->DisableIncrementalBarrier();
  }

  Prologue();

  const int num_tasks = FLAG_marker_tasks;
  RELEASE_ASSERT(num_tasks > 0);
  ThreadBarrier* barrier = new ThreadBarrier(num_tasks, /*initial=*/1);

  ResetSlices();
  // Used to coordinate draining among tasks; all start out as 'busy'.
  RelaxedAtomic<uintptr_t> num_busy = 0;

  IntrusiveDList<SafepointTask> tasks;
  for (intptr_t i = 0; i < num_tasks; ++i) {
    MarkingVisitor* visitor = visitors_[i];
    // Visitors may or may not have already been created depending on
    // whether we did some concurrent marking.
    if (visitor == nullptr) {
      visitor = new MarkingVisitor(
          isolate_group_, page_space, &old_marking_stack_, &new_marking_stack_,
          &tlab_deferred_marking_stack_, &deferred_marking_stack_);
      visitors_[i] = visitor;
    }

    // Move all work from local blocks to the global list. Any given
    // visitor might not get to run if it fails to reach TryEnter soon
    // enough, and we must fail to visit objects but they're sitting in
    // such a visitor's local blocks.
    visitor->Flush(&global_list_);
    // Need to move weak property list too.
    tasks.Append(new ParallelMarkTask(this, isolate_group_, &old_marking_stack_,
                                      barrier, visitor, &num_busy));
  }
  visitors_[0]->Adopt(&global_list_);
  isolate_group_->safepoint_handler()->RunTasks(&tasks);

  for (intptr_t i = 0; i < num_tasks; i++) {
    MarkingVisitor* visitor = visitors_[i];
    visitor->FinalizeMarking();
    marked_bytes_ += visitor->marked_bytes();
    marked_micros_ += visitor->marked_micros();
    delete visitor;
    visitors_[i] = nullptr;
  }

  ASSERT(global_list_.IsEmpty());

  // Separate from verify_after_gc because that verification interferes with
  // concurrent marking.
  if (FLAG_verify_after_marking) {
    VerifyAfterMarkingVisitor visitor;
    heap_->VisitObjects(&visitor);
    if (visitor.failed()) {
      FATAL("verify after marking");
    }
  }

  Epilogue();
}

void GCMarker::PruneWeak(Scavenger* scavenger) {
  scavenger->PruneWeak(&global_list_);
  for (intptr_t i = 0, n = FLAG_marker_tasks; i < n; i++) {
    scavenger->PruneWeak(visitors_[i]->delayed());
  }
}

}  // namespace dart
