Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

SSL Marking.cpp

  Interaktion und
PortierbarkeitC
 

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "gc/Marking-inl.h"

#include "mozilla/DebugOnly.h"
#include "mozilla/IntegerRange.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/Maybe.h"
#include "mozilla/PodOperations.h"
#include "mozilla/ScopeExit.h"

#include <algorithm>
#include <type_traits>

#include "debugger/Debugger.h"
#include "gc/BufferAllocator.h"
#include "gc/GCInternals.h"
#include "gc/ParallelMarking.h"
#include "gc/TraceKind.h"
#include "jit/JitCode.h"
#include "js/GCTypeMacros.h"  // JS_FOR_EACH_PUBLIC_{,TAGGED_}GC_POINTER_TYPE
#include "js/SliceBudget.h"
#include "util/Poison.h"
#include "util/RandomSeed.h"
#include "vm/GeneratorObject.h"

#include "gc/BufferAllocator-inl.h"
#include "gc/GC-inl.h"
#include "gc/PrivateIterators-inl.h"
#include "gc/TraceMethods-inl.h"
#include "gc/WeakMap-inl.h"
#include "vm/GeckoProfiler-inl.h"

using namespace js;
using namespace js::gc;

using JS::MapTypeToTraceKind;
using JS::SliceBudget;

using mozilla::DebugOnly;
using mozilla::IntegerRange;

// [SMDOC] GC Tracing
//
// Tracing Overview
// ================
//
// Tracing, in this context, refers to an abstract visitation of some or all of
// the GC-controlled heap. The effect of tracing an edge of the graph depends
// on the subclass of the JSTracer on whose behalf we are tracing.
//
// Marking
// -------
//
// The primary JSTracer is the GCMarker. The marking tracer causes the target
// of each traversed edge to be marked black and the target edge's children to
// be marked either gray (in the gc algorithm sense) or immediately black.
//
// Callback
// --------
//
// The secondary JSTracer is the CallbackTracer. This simply invokes a callback
// on each edge in a child.
//
// The following is a rough outline of the general struture of the tracing
// internals.
//
/* clang-format off */
//
//  +-------------------+                             ......................
//  |                   |                             :                    :
//  |                   v                             v                +---+---+
//  |   TraceRoot   TraceEdge   TraceRange        GCMarker::           |       |
//  |       |           |           |         processMarkStackTop      | Mark  |
//  |       +-----------------------+                 |                | Stack |
//  |                   |                             |                |       |
//  |                   v                             |                +---+---+
//  |           TraceEdgeInternal                     |                    ^
//  |                   |                             +<-------------+     :
//  |                   |                             |              |     :
//  |                   v                             v              |     :
//  |            CallbackTracer::             markAndTraverseEdge    |     :
//  |              onSomeEdge                         |              |     :
//  |                   |                             |              |     :
//  |                   |                             |              |     :
//  |                   +-------------+---------------+              |     :
//  |                                 |                              |     :
//  |                                 v                              |     :
//  |                          markAndTraverse                       |     :
//  |                                 |                              |     :
//  |                                 |                              |     :
//  |                              traverse                          |     :
//  |                                 |                              |     :
//  |             +--------------------------------------+           |     :
//  |             |                   |                  |           |     :
//  |             v                   v                  v           |     :
//  |    markAndTraceChildren    markAndPush    eagerlyMarkChildren  |     :
//  |             |                   :                  |           |     :
//  |             v                   :                  +-----------+     :
//  |      T::traceChildren           :                                    :
//  |             |                   :                                    :
//  +-------------+                   ......................................
//
//   Legend:
//     ------- Direct calls
//     ....... Data flow
//
/* clang-format on */

static const size_t ValueRangeWords =
    sizeof(MarkStack::SlotsOrElementsRange) / sizeof(uintptr_t);

/*** Tracing Invariants *****************************************************/

template <typename T>
static inline bool IsOwnedByOtherRuntime(JSRuntime* rt, T thing) {
  bool other = thing->runtimeFromAnyThread() != rt;
  MOZ_ASSERT_IF(other, thing->isPermanentAndMayBeShared());
  return other;
}

#ifdef DEBUG

static inline bool IsInFreeList(TenuredCell* cell) {
  Arena* arena = cell->arena();
  uintptr_t addr = reinterpret_cast<uintptr_t>(cell);
  MOZ_ASSERT(Arena::isAligned(addr, arena->getThingSize()));
  return arena->inFreeList(addr);
}

template <typename T>
void js::CheckTracedThing(JSTracer* trc, T* thing) {
  MOZ_ASSERT(trc);

  if (!thing) {
    return;
  }

  if (IsForwarded(thing)) {
    JS::TracerKind kind = trc->kind();
    MOZ_ASSERT(kind == JS::TracerKind::Tenuring ||
               kind == JS::TracerKind::MinorSweeping ||
               kind == JS::TracerKind::Moving ||
               kind == JS::TracerKind::HeapCheck);
    thing = Forwarded(thing);
  }

  /* This function uses data that's not available in the nursery. */
  if (IsInsideNursery(thing)) {
    return;
  }

  /*
   * Permanent shared things that are not associated with this runtime will be
   * ignored during marking.
   */

  Zone* zone = thing->zoneFromAnyThread();
  if (IsOwnedByOtherRuntime(trc->runtime(), thing)) {
    MOZ_ASSERT(!zone->wasGCStarted());
    MOZ_ASSERT(thing->isMarkedBlack());
    return;
  }

  JSRuntime* rt = trc->runtime();
  MOZ_ASSERT(zone->runtimeFromAnyThread() == rt);

  bool isGcMarkingTracer = trc->isMarkingTracer();
  bool isUnmarkGrayTracer = IsTracerKind(trc, JS::TracerKind::UnmarkGray);
  bool isClearEdgesTracer = IsTracerKind(trc, JS::TracerKind::ClearEdges);

  if (TlsContext.get()) {
    // If we're on the main thread we must have access to the runtime and zone.
    MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
    MOZ_ASSERT(CurrentThreadCanAccessZone(zone));
  } else {
    MOZ_ASSERT(isGcMarkingTracer || isUnmarkGrayTracer || isClearEdgesTracer ||
               IsTracerKind(trc, JS::TracerKind::Moving) ||
               IsTracerKind(trc, JS::TracerKind::Sweeping));
    MOZ_ASSERT_IF(!isClearEdgesTracer, CurrentThreadIsPerformingGC());
  }

  MOZ_ASSERT(thing->isAligned());
  MOZ_ASSERT(MapTypeToTraceKind<std::remove_pointer_t<T>>::kind ==
             thing->getTraceKind());

  /*
   * Check that we only mark allocated cells.
   *
   * This check is restricted to marking for two reasons: Firstly, if background
   * sweeping is running and concurrently modifying the free list then it is not
   * safe. Secondly, it was thought to be slow so this is a compromise so as to
   * not affect test times too much.
   */

  MOZ_ASSERT_IF(zone->isGCMarking(), !IsInFreeList(&thing->asTenured()));
}

template <typename T>
void js::CheckTracedThing(JSTracer* trc, const T& thing) {
  ApplyGCThingTyped(thing, [trc](auto t) { CheckTracedThing(trc, t); });
}

template <typename T>
static void CheckMarkedThing(GCMarker* gcMarker, T* thing) {
  Zone* zone = thing->zoneFromAnyThread();

  MOZ_ASSERT(zone->shouldMarkInZone(gcMarker->markColor()) ||
             zone->isAtomsZone());

  MOZ_ASSERT_IF(gcMarker->shouldCheckCompartments(),
                zone->isCollectingFromAnyThread() || zone->isAtomsZone());

  MOZ_ASSERT_IF(gcMarker->markColor() == MarkColor::Gray,
                !zone->isGCMarkingBlackOnly() || zone->isAtomsZone());

  MOZ_ASSERT(!(zone->isGCSweeping() || zone->isGCFinished() ||
               zone->isGCCompacting()));

  // Check that we don't stray from the current compartment and zone without
  // using TraceCrossCompartmentEdge.
  Compartment* comp = thing->maybeCompartment();
  MOZ_ASSERT_IF(gcMarker->tracingCompartment && comp,
                gcMarker->tracingCompartment == comp);
  MOZ_ASSERT_IF(gcMarker->tracingZone,
                gcMarker->tracingZone == zone || zone->isAtomsZone());
}

namespace js {

#  define IMPL_CHECK_TRACED_THING(_, type, _1, _2) \
    template void CheckTracedThing<type>(JSTracer*, type*);
JS_FOR_EACH_TRACEKIND(IMPL_CHECK_TRACED_THING);
#  undef IMPL_CHECK_TRACED_THING

template void CheckTracedThing<Value>(JSTracer*, const Value&);
template void CheckTracedThing<wasm::AnyRef>(JSTracer*, const wasm::AnyRef&);

}  // namespace js

#endif

static inline bool ShouldMarkCrossCompartment(GCMarker* marker, JSObject* src,
                                              Cell* dstCell, const char* name) {
#ifdef DEBUG
  if (src->isMarkedGray() && !dstCell->isTenured()) {
    // Bug 1743098: This shouldn't be possible but it does seem to happen. Log
    // some useful information in debug builds.
    SEprinter printer;
    printer.printf(
        "ShouldMarkCrossCompartment: cross compartment edge '%s' from gray "
        "object to nursery thing\n",
        name);
    printer.put("src: ");
    src->dump(printer);
    printer.put("dst: ");
    dstCell->dump(printer);
    MOZ_CRASH("Found cross compartment edge from gray object to nursery thing");
  }
#endif

  CellColor targetColor = AsCellColor(marker->markColor());
  CellColor currentColor = dstCell->color();
  if (currentColor >= targetColor) {
    // Cell is already sufficiently marked. Nothing to do.
    return false;
  }

  TenuredCell& dst = dstCell->asTenured();
  JS::Zone* dstZone = dst.zone();
  if (!src->zone()->isGCMarking() && !dstZone->isGCMarking()) {
    return false;
  }

  if (targetColor == CellColor::Black) {
    // Check our sweep groups are correct: we should never have to
    // mark something in a zone that we have started sweeping.
    MOZ_ASSERT(currentColor < CellColor::Black);
    MOZ_ASSERT(!dstZone->isGCSweeping());

    /*
     * Having black->gray edges violates our promise to the cycle collector so
     * we ensure that gray things we encounter when marking black end up getting
     * marked black.
     *
     * This can happen for two reasons:
     *
     * 1) If we're collecting a compartment and it has an edge to an uncollected
     * compartment it's possible that the source and destination of the
     * cross-compartment edge should be gray, but the source was marked black by
     * the write barrier.
     *
     * 2) If we yield during gray marking and the write barrier marks a gray
     * thing black.
     *
     * We handle the first case before returning whereas the second case happens
     * as part of normal marking.
     */

    if (currentColor == CellColor::Gray && !dstZone->isGCMarking()) {
      UnmarkGrayGCThingUnchecked(marker,
                                 JS::GCCellPtr(&dst, dst.getTraceKind()));
      return false;
    }

    return dstZone->isGCMarking();
  }

  // Check our sweep groups are correct as above.
  MOZ_ASSERT(currentColor == CellColor::White);
  MOZ_ASSERT(!dstZone->isGCSweeping());

  if (dstZone->isGCMarkingBlackOnly()) {
    /*
     * The destination compartment is being not being marked gray now,
     * but it will be later, so record the cell so it can be marked gray
     * at the appropriate time.
     */

    DelayCrossCompartmentGrayMarking(marker, src);
    return false;
  }

  return dstZone->isGCMarkingBlackAndGray();
}

static bool ShouldTraceCrossCompartment(JSTracer* trc, JSObject* src,
                                        Cell* dstCell, const char* name) {
  if (!trc->isMarkingTracer()) {
    return true;
  }

  return ShouldMarkCrossCompartment(GCMarker::fromTracer(trc), src, dstCell,
                                    name);
}

static bool ShouldTraceCrossCompartment(JSTracer* trc, JSObject* src,
                                        const Value& val, const char* name) {
  return val.isGCThing() &&
         ShouldTraceCrossCompartment(trc, src, val.toGCThing(), name);
}

template <typename T>
static inline bool ShouldMark(MarkColor color, T* thing) {
  // We may encounter nursery things during normal marking since we don't
  // collect the nursery at the start of every GC slice.
  if (!thing->isTenured()) {
    return false;
  }

  // Allow marking symbols even if we're not collecting the atoms zone. This is
  // necessary to unmark gray symbols during an incremental GC. Failing to do
  // this will break our promise to the cycle collector that there are no black
  // to gray edges.
  if (std::is_same_v<T, JS::Symbol> && color == MarkColor::Black) {
    return true;
  }

  // Otherwise don't mark things outside a collected zone if we are in a
  // per-zone GC. Don't mark permanent shared things owned by other runtimes (we
  // will never observe their zone being collected).
  Zone* zone = thing->asTenured().zoneFromAnyThread();
  return zone->shouldMarkInZone(color);
}

#ifdef DEBUG

template <typename T>
void js::gc::AssertShouldMarkInZone(GCMarker* marker, T* thing) {
  if (thing->isMarkedBlack()) {
    return;
  }

  Zone* zone = thing->zone();
  MOZ_ASSERT(zone->shouldMarkInZone(marker->markColor()) ||
             zone->isAtomsZone());
}

void js::gc::AssertRootMarkingPhase(JSTracer* trc) {
  MOZ_ASSERT_IF(trc->isMarkingTracer(),
                trc->runtime()->gc.state() == State::NotActive ||
                    trc->runtime()->gc.state() == State::MarkRoots);
}

#endif  // DEBUG

/*** Tracing Interface ******************************************************/

template <typename T>
static void TraceExternalEdgeHelper(JSTracer* trc, T* thingp,
                                    const char* name) {
  TraceEdgeInternal(trc, ConvertToBase(thingp), name);
}

JS_PUBLIC_API void js::UnsafeTraceManuallyBarrieredEdge(JSTracer* trc,
                                                        JSObject** thingp,
                                                        const char* name) {
  TraceEdgeInternal(trc, ConvertToBase(thingp), name);
}

template <typename T>
static void TraceRootHelper(JSTracer* trc, T* thingp, const char* name) {
  MOZ_ASSERT(thingp);
  js::TraceRoot(trc, thingp, name);
}

namespace js {
class AbstractGeneratorObject;
class SavedFrame;
}  // namespace js

#define DEFINE_TRACE_EXTERNAL_EDGE_FUNCTION(type)                           \
  JS_PUBLIC_API void js::gc::TraceExternalEdge(JSTracer* trc, type* thingp, \
                                               const char* name) {          \
    TraceExternalEdgeHelper(trc, thingp, name);                             \
  }

// Define TraceExternalEdge for each public GC pointer type.
JS_FOR_EACH_PUBLIC_GC_POINTER_TYPE(DEFINE_TRACE_EXTERNAL_EDGE_FUNCTION)
JS_FOR_EACH_PUBLIC_TAGGED_GC_POINTER_TYPE(DEFINE_TRACE_EXTERNAL_EDGE_FUNCTION)

#undef DEFINE_TRACE_EXTERNAL_EDGE_FUNCTION

#define DEFINE_UNSAFE_TRACE_ROOT_FUNCTION(type)                 \
  JS_PUBLIC_API void JS::TraceRoot(JSTracer* trc, type* thingp, \
                                   const char* name) {          \
    TraceRootHelper(trc, thingp, name);                         \
  }

// Define TraceRoot for each public GC pointer type.
JS_FOR_EACH_PUBLIC_GC_POINTER_TYPE(DEFINE_UNSAFE_TRACE_ROOT_FUNCTION)
JS_FOR_EACH_PUBLIC_TAGGED_GC_POINTER_TYPE(DEFINE_UNSAFE_TRACE_ROOT_FUNCTION)

// Also, for the moment, define TraceRoot for internal GC pointer types.
DEFINE_UNSAFE_TRACE_ROOT_FUNCTION(AbstractGeneratorObject*)
DEFINE_UNSAFE_TRACE_ROOT_FUNCTION(SavedFrame*)
DEFINE_UNSAFE_TRACE_ROOT_FUNCTION(wasm::AnyRef)

#undef DEFINE_UNSAFE_TRACE_ROOT_FUNCTION

namespace js::gc {

#define INSTANTIATE_INTERNAL_TRACE_FUNCTIONS(type)                     \
  template void TraceRangeInternal<type>(JSTracer*, size_t len, type*, \
                                         const char*);

#define INSTANTIATE_INTERNAL_TRACE_FUNCTIONS_FROM_TRACEKIND(_1, type, _2, _3) \
  INSTANTIATE_INTERNAL_TRACE_FUNCTIONS(type*)

JS_FOR_EACH_TRACEKIND(INSTANTIATE_INTERNAL_TRACE_FUNCTIONS_FROM_TRACEKIND)
JS_FOR_EACH_PUBLIC_TAGGED_GC_POINTER_TYPE(INSTANTIATE_INTERNAL_TRACE_FUNCTIONS)
INSTANTIATE_INTERNAL_TRACE_FUNCTIONS(TaggedProto)

#undef INSTANTIATE_INTERNAL_TRACE_FUNCTIONS_FROM_TRACEKIND
#undef INSTANTIATE_INTERNAL_TRACE_FUNCTIONS

}  // namespace js::gc

// Records the source zone (and, in debug builds, compartment) before calling
// a trace hook or traceChildren() method on a GC thing. The source zone is
// required in all builds so that MarkingTracerT::onEdge can keep the per-zone
// atom-marking bitmap in sync for Symbol edges traced via the generic tracer.
class MOZ_RAII AutoSetTracingSource {
  GCMarker* marker = nullptr;

 public:
  template <typename T>
  AutoSetTracingSource(JSTracer* trc, T* thing) {
    if (trc->isMarkingTracer() && thing) {
      marker = GCMarker::fromTracer(trc);
      MOZ_ASSERT(!marker->tracingZone);
      marker->tracingZone = thing->asTenured().zone();
#ifdef DEBUG
      MOZ_ASSERT(!marker->tracingCompartment);
      marker->tracingCompartment = thing->maybeCompartment();
#endif
    }
  }

  ~AutoSetTracingSource() {
    if (marker) {
      marker->tracingZone = nullptr;
#ifdef DEBUG
      marker->tracingCompartment = nullptr;
#endif
    }
  }
};

// Clear the tracing source. This happens after the trace hook has called back
// into one of our trace APIs and we've checked the traced thing, before any
// nested traversal that may itself use AutoSetTracingSource.
class MOZ_RAII AutoClearTracingSource {
  GCMarker* marker = nullptr;
  JS::Zone* prevZone = nullptr;
#ifdef DEBUG
  Compartment* prevCompartment = nullptr;
#endif

  void init(GCMarker* marker) {
    this->marker = marker;
    prevZone = marker->tracingZone;
    marker->tracingZone = nullptr;
#ifdef DEBUG
    prevCompartment = marker->tracingCompartment;
    marker->tracingCompartment = nullptr;
#endif
  }

 public:
  explicit AutoClearTracingSource(JSTracer* trc) {
    if (trc->isMarkingTracer()) {
      init(GCMarker::fromTracer(trc));
    }
  }
  explicit AutoClearTracingSource(GCMarker* marker) { init(marker); }
  ~AutoClearTracingSource() {
    if (marker) {
      marker->tracingZone = prevZone;
#ifdef DEBUG
      marker->tracingCompartment = prevCompartment;
#endif
    }
  }
};

template <typename T>
void js::TraceManuallyBarrieredCrossCompartmentEdge(JSTracer* trc,
                                                    JSObject* src, T* dst,
                                                    const char* name) {
  // Clear expected compartment for cross-compartment edge.
  AutoClearTracingSource acts(trc);

  if (ShouldTraceCrossCompartment(trc, src, *dst, name)) {
    TraceEdgeInternal(trc, dst, name);
  }
}
template void js::TraceManuallyBarrieredCrossCompartmentEdge<Value>(
    JSTracer*, JSObject*, Value*, const char*);
template void js::TraceManuallyBarrieredCrossCompartmentEdge<JSObject*>(
    JSTracer*, JSObject*, JSObject**, const char*);
template void js::TraceManuallyBarrieredCrossCompartmentEdge<BaseScript*>(
    JSTracer*, JSObject*, BaseScript**, const char*);

template <typename T>
void js::TraceSameZoneCrossCompartmentEdge(JSTracer* trc,
                                           const BarrieredBase<T>* dst,
                                           const char* name) {
#ifdef DEBUG
  if (trc->isMarkingTracer()) {
    T thing = *dst->unbarrieredAddress();
    MOZ_ASSERT(thing->maybeCompartment(),
               "Use TraceEdge for GC things without a compartment");

    GCMarker* gcMarker = GCMarker::fromTracer(trc);
    MOZ_ASSERT_IF(gcMarker->tracingZone,
                  thing->zone() == gcMarker->tracingZone);
  }

  // Skip compartment checks for this edge.
  if (trc->kind() == JS::TracerKind::CompartmentCheck) {
    return;
  }
#endif

  // Clear expected compartment for cross-compartment edge.
  AutoClearTracingSource acts(trc);
  TraceEdgeInternal(trc, ConvertToBase(dst->unbarrieredAddress()), name);
}
template void js::TraceSameZoneCrossCompartmentEdge(
    JSTracer*, const BarrieredBase<Shape*>*, const char*);

template <typename T>
void js::TraceWeakMapKeyEdgeInternal(JSTracer* trc, Zone* weakMapZone,
                                     T** thingp, const char* name) {
  // We'd like to assert that the the thing's zone is currently being marked but
  // that's not always true when tracing debugger weak maps which have keys in
  // other compartments.

  // Clear expected compartment for cross-compartment edge.
  AutoClearTracingSource acts(trc);

  TraceEdgeInternal(trc, thingp, name);
}

template <typename T>
void js::TraceWeakMapKeyEdgeInternal(JSTracer* trc, Zone* weakMapZone,
                                     T* thingp, const char* name) {
  // We can't use ShouldTraceCrossCompartment here because that assumes the
  // source of the edge is a CCW object which could be used to delay gray
  // marking. Instead, assert that the weak map zone is in the same marking
  // state as the target thing's zone and therefore we can go ahead and mark it.
#ifdef DEBUG
  if (trc->isMarkingTracer()) {
    MOZ_ASSERT(weakMapZone->isGCMarking());
    MOZ_ASSERT(weakMapZone->gcState() ==
               gc::ToMarkable(*thingp)->zone()->gcState());
  }
#endif

  // Clear expected compartment for cross-compartment edge.
  AutoClearTracingSource acts(trc);

  TraceEdgeInternal(trc, thingp, name);
}

template void js::TraceWeakMapKeyEdgeInternal<JSObject>(JSTracer*, Zone*,
                                                        JSObject**,
                                                        const char*);
template void js::TraceWeakMapKeyEdgeInternal<BaseScript>(JSTracer*, Zone*,
                                                          BaseScript**,
                                                          const char*);
template void js::TraceWeakMapKeyEdgeInternal<JS::Value>(JSTracer*, Zone*,
                                                         JS::Value*,
                                                         const char*);

static Cell* TraceGenericPointerRootAndType(JSTracer* trc, Cell* thing,
                                            JS::TraceKind kind,
                                            const char* name) {
  return MapGCThingTyped(thing, kind, [trc, name](auto t) -> Cell* {
    TraceRoot(trc, &t, name);
    return t;
  });
}

void js::TraceGenericPointerRoot(JSTracer* trc, Cell** thingp,
                                 const char* name) {
  MOZ_ASSERT(thingp);
  Cell* thing = *thingp;
  if (!thing) {
    return;
  }

  Cell* traced =
      TraceGenericPointerRootAndType(trc, thing, thing->getTraceKind(), name);
  if (traced != thing) {
    *thingp = traced;
  }
}

void js::TraceManuallyBarrieredGenericPointerEdge(JSTracer* trc, Cell** thingp,
                                                  const char* name) {
  MOZ_ASSERT(thingp);
  Cell* thing = *thingp;
  if (!*thingp) {
    return;
  }

  auto* traced = MapGCThingTyped(thing, thing->getTraceKind(),
                                 [trc, name](auto t) -> Cell* {
                                   TraceManuallyBarrieredEdge(trc, &t, name);
                                   return t;
                                 });
  if (traced != thing) {
    *thingp = traced;
  }
}

void js::TraceGCCellPtrRoot(JSTracer* trc, JS::GCCellPtr* thingp,
                            const char* name) {
  Cell* thing = thingp->asCell();
  if (!thing) {
    return;
  }

  Cell* traced =
      TraceGenericPointerRootAndType(trc, thing, thingp->kind(), name);

  if (!traced) {
    *thingp = JS::GCCellPtr();
  } else if (traced != thingp->asCell()) {
    *thingp = JS::GCCellPtr(traced, thingp->kind());
  }
}

void js::TraceManuallyBarrieredGCCellPtr(JSTracer* trc, JS::GCCellPtr* thingp,
                                         const char* name) {
  Cell* thing = thingp->asCell();
  if (!thing) {
    return;
  }

  Cell* traced = MapGCThingTyped(thing, thing->getTraceKind(),
                                 [trc, name](auto t) -> Cell* {
                                   TraceManuallyBarrieredEdge(trc, &t, name);
                                   return t;
                                 });

  if (!traced) {
    // If we are clearing edges, also erase the type. This happens when using
    // ClearEdgesTracer.
    *thingp = JS::GCCellPtr();
  } else if (traced != thingp->asCell()) {
    *thingp = JS::GCCellPtr(traced, thingp->kind());
  }
}

template <typename T>
inline bool TraceTaggedPtrEdge(JSTracer* trc, T* thingp, const char* name) {
  T thing;
#ifdef JS_GC_CONCURRENT_MARKING
  // Conservatively perform an atomic load even when marking is not concurrent.
  thing = thingp->atomicGet();
#else
  thing = *thingp;
#endif

  // Return true by default. For some types the lambda below won't be called.
  bool ret = true;
  auto result = MapGCThingTyped(thing, [&](auto ptr) {
    if (!TraceEdgeInternal(trc, &ptr, name)) {
      ret = false;
      return TaggedPtr<T>::empty();
    }

    return TaggedPtr<T>::wrap(ptr);
  });

  // Only update *thingp if the value changed, to avoid TSan false positives for
  // template objects when using DumpHeapTracer or UbiNode tracers while Ion
  // compiling off-thread.
  if (result.isSome() && result.value() != thing) {
    *thingp = result.value();
  }

  return ret;
}

bool js::gc::TraceEdgeInternal(JSTracer* trc, Value* thingp, const char* name) {
  return TraceTaggedPtrEdge(trc, thingp, name);
}
bool js::gc::TraceEdgeInternal(JSTracer* trc, jsid* thingp, const char* name) {
  return TraceTaggedPtrEdge(trc, thingp, name);
}
bool js::gc::TraceEdgeInternal(JSTracer* trc, TaggedProto* thingp,
                               const char* name) {
  return TraceTaggedPtrEdge(trc, thingp, name);
}
bool js::gc::TraceEdgeInternal(JSTracer* trc, wasm::AnyRef* thingp,
                               const char* name) {
  return TraceTaggedPtrEdge(trc, thingp, name);
}

template <typename T>
void js::gc::TraceRangeInternal(JSTracer* trc, size_t len, T* vec,
                                const char* name) {
  JS::AutoTracingIndex index(trc);
  for (auto i : IntegerRange(len)) {
    if (InternalBarrierMethods<T>::isMarkable(vec[i])) {
      TraceEdgeInternal(trc, &vec[i], name);
    }
    ++index;
  }
}

/*** GC Marking Interface ***************************************************/

template <uint32_t opts>
void MarkingTracerT<opts>::markEphemeronEdges(EphemeronEdgeVector& edges,
                                              gc::MarkColor srcColor) {
  // This is only called as part of GC weak marking.
  static_assert(hasOption(MarkingOptions::MarkImplicitEdges));

  DebugOnly<size_t> initialLength = edges.length();

  for (auto& edge : edges) {
    MarkColor targetColor = std::min(srcColor, MarkColor(edge.color()));
    MOZ_ASSERT(markColor() >= targetColor);
    if (targetColor == markColor()) {
      ApplyGCThingTyped(edge.target(), edge.target()->getTraceKind(),
                        [this](auto t) { this->markAndTraverse(t); });
    }
  }

  // The above marking always goes through pushThing, which will not cause
  // 'edges' to be appended to while iterating.
  MOZ_ASSERT(edges.length() == initialLength);

  // During the black marking Zone::enterWeakMarkingMode, erase black ephemerons
  // whose sources are black. These have now been handled and are no longer
  // needed.
  //
  // This is required for correctness because (1) nuking a CCW conservatively
  // marks through the related edges and then loses the CCW->target connection
  // that induces a sweep group edge. As a result, it is possible for the
  // delegate zone to get marked later, look up an edge in this table, and then
  // try to mark something in a Zone that is no longer marking.
  //
  // (2), the gray pass only wants to visit things that will be marked gray. If
  // a gray src in a black ephemeron got barrier-marked black, then we'd end up
  // visiting a value that should be marked black. We could skip such things,
  // but since they need to be removed anyway as per (1), we rely on that
  // removal and assert above that we don't need to mark darker than the current
  // mark color.
  if (srcColor == MarkColor::Black && markColor() == MarkColor::Black) {
    edges.eraseIf([](auto& edge) { return edge.color() == MarkColor::Black; });
  }
}

template <typename T>
struct TypeCanHaveImplicitEdges : std::false_type {};
template <>
struct TypeCanHaveImplicitEdges<JSObject> : std::true_type {};
template <>
struct TypeCanHaveImplicitEdges<BaseScript> : std::true_type {};
template <>
struct TypeCanHaveImplicitEdges<JS::Symbol> : std::true_type {};

template <uint32_t opts>
template <typename T>
void MarkingTracerT<opts>::maybeMarkImplicitEdges(T* markedThing) {
  if constexpr (hasOption(MarkingOptions::MarkImplicitEdges) &&
                TypeCanHaveImplicitEdges<T>::value) {
    markImplicitEdges(markedThing);
  }
}

template <uint32_t opts>
template <typename T>
void MarkingTracerT<opts>::markImplicitEdges(T* markedThing) {
  static_assert(hasOption(MarkingOptions::MarkImplicitEdges));
  static_assert(TypeCanHaveImplicitEdges<T>::value);

  Zone* zone = markedThing->asTenured().zone();
  MOZ_ASSERT(zone->isGCMarking() || zone->isAtomsZone());
  MOZ_ASSERT(!zone->isGCSweeping());

  auto& ephemeronTable = zone->gcEphemeronEdges();
  auto p = ephemeronTable.lookup(&markedThing->asTenured());
  if (!p) {
    return;
  }

  EphemeronEdgeVector& edges = p->value();

  // markedThing might be a key in a debugger weakmap, which can end up
  // marking values that are in a different compartment.
  AutoClearTracingSource acts(this);

  // If markedThing is now gray, then it won't be on the black mark stack, so we
  // won't see it while marking black. But we could have the other way around:
  // markedThing was gray when it was pushed on the (gray) mark stack, but was
  // later marked black, and we're marking gray.
  MOZ_ASSERT(CellColor(markColor()) <= markedThing->color());

  // No need to consider EffectiveColor; we know it's on the mark stack, so it
  // must be in a collected zone (asserted above).
  markEphemeronEdges(edges, AsMarkColor(markedThing->color()));

  if (edges.empty()) {
    ephemeronTable.remove(p);
  }
}

template <uint32_t opts>
MarkingTracerT<opts>::MarkingTracerT(JSRuntime* runtime, GCMarker* marker)
    : GenericTracerImpl<MarkingTracerT<opts>>(
          runtime, JS::TracerKind::Marking,
          JS::TraceOptions(JS::WeakMapTraceAction::Expand,
                           JS::WeakEdgeTraceAction::Skip)) {
  // Marking tracers are owned by (and part of) a GCMarker.
  MOZ_ASSERT(this == marker->tracer());
  MOZ_ASSERT(gcMarker() == marker);
}

template <uint32_t opts>
MOZ_ALWAYS_INLINE GCMarker* MarkingTracerT<opts>::gcMarker() {
  return GCMarker::fromTracer(this);
}
template <uint32_t opts>
MOZ_ALWAYS_INLINE const GCMarker* MarkingTracerT<opts>::gcMarker() const {
  return GCMarker::fromTracer(const_cast<MarkingTracerT<opts>*>(this));
}

// Unmark gray symbols in incremental GC: gray unmarking doesn't proceed through
// zones which are currently being marked incrementally because the marking
// state isn't consistent, and we handle this later as part of marking.
static inline void MaybeUnmarkGraySymbol(JSRuntime* runtime,
                                         JS::Zone* sourceZone,
                                         JS::Symbol* target) {
  // Ignore edges from self-hosted JitCode that lives in the atoms zone.
  if (sourceZone->isAtomsZone()) {
    return;
  }

  AtomMarkingRuntime& atomMarking = runtime->gc.atomMarking;
  MOZ_ASSERT(atomMarking.atomIsMarked(sourceZone, target));
  atomMarking.maybeUnmarkGrayAtomically(sourceZone, target);
}

template <uint32_t opts>
template <typename T>
bool MarkingTracerT<opts>::onEdge(T** thingp, const char* name) {
  T* thing;
  if constexpr (bool(opts & MarkingOptions::ConcurrentMarking)) {
    thing = __atomic_load_n(thingp, __ATOMIC_RELAXED);
  } else {
    thing = *thingp;
  }

  if (!thing) {
    return true;
  }

  // Do per-type marking precondition checks.
  if (!ShouldMark(markColor(), thing)) {
    MOZ_ASSERT(gc::detail::GetEffectiveColor(gcMarker(), thing) ==
               js::gc::CellColor::Black);
    return true;
  }

  MOZ_ASSERT_IF(IsOwnedByOtherRuntime(this->runtime(), thing),
                thing->isMarkedBlack());

  if constexpr (std::is_same_v<T, JS::Symbol>) {
    Zone* zone = tracingZone();
    if (markColor() == MarkColor::Black && zone) {
      MaybeUnmarkGraySymbol(this->runtime(), zone, thing);
    }
  }

#ifdef DEBUG
  CheckMarkedThing(gcMarker(), thing);
#endif

  AutoClearTracingSource acts(this);
  this->markAndTraverse(thing);

  if constexpr (hasOption(MarkingOptions::MarkRootCompartments)) {
    // Mark the compartment as live.
    SetCompartmentHasMarkedCells(thing);
  }

  return true;
}

#define INSTANTIATE_ONEDGE_METHOD(name, type, _1, _2)                 \
  template bool MarkingTracerT<MarkingOptions::None>::onEdge<type>(   \
      type * *thingp, const char* name);                              \
  template bool                                                       \
  MarkingTracerT<MarkingOptions::MarkImplicitEdges>::onEdge<type>(    \
      type * *thingp, const char* name);                              \
  template bool                                                       \
  MarkingTracerT<MarkingOptions::MarkRootCompartments>::onEdge<type>( \
      type * *thingp, const char* name);
JS_FOR_EACH_TRACEKIND(INSTANTIATE_ONEDGE_METHOD)
#undef INSTANTIATE_ONEDGE_METHOD

static void TraceEdgeForBarrier(GCMarker* gcmarker, TenuredCell* thing,
                                JS::TraceKind kind) {
  // Dispatch to markAndTraverse without checking ShouldMark.

#ifdef DEBUG
  MOZ_ASSERT(gcmarker->markColor() == MarkColor::Black);
  AutoSetThreadIsMarking threadIsMarking;
#endif  // DEBUG

  AutoClearTracingSource acts(gcmarker);

  ApplyGCThingTyped(thing, kind, [gcmarker](auto thing) {
    MOZ_ASSERT(ShouldMark(MarkColor::Black, thing));
    gcmarker->matchRegularOrParallelTracer([thing](auto& trc) {
      CheckTracedThing(&trc, thing);
      trc.markAndTraverse(thing);
    });
  });
}

JS_PUBLIC_API void js::gc::PerformIncrementalReadBarrier(JS::GCCellPtr thing) {
  // Optimized marking for read barriers. This is called from
  // ExposeGCThingToActiveJS which has already checked the prerequisites for
  // performing a read barrier. This means we can skip a bunch of checks and
  // call into the tracer directly.

  MOZ_ASSERT(thing);
  MOZ_ASSERT(!JS::RuntimeHeapIsCollecting());

  TenuredCell* cell = &thing.asCell()->asTenured();

#ifndef JS_GC_CONCURRENT_MARKING
  MOZ_ASSERT(!cell->isMarkedBlack());
#endif

  Zone* zone = cell->zone();
  MOZ_ASSERT(zone->needsMarkingBarrier());

  // Skip dispatching on known tracer type.
  GCMarker* gcmarker = GCMarker::fromTracer(zone->barrierTracer());
  TraceEdgeForBarrier(gcmarker, cell, thing.kind());
}

void js::gc::PerformIncrementalReadBarrier(TenuredCell* cell) {
  // Internal version of previous function.

  MOZ_ASSERT(cell);
  MOZ_ASSERT(!JS::RuntimeHeapIsCollecting());

  if (cell->isMarkedBlack()) {
    return;
  }

  Zone* zone = cell->zone();
  MOZ_ASSERT(zone->needsMarkingBarrier());

  // Skip dispatching on known tracer type.
  GCMarker* gcmarker = GCMarker::fromTracer(zone->barrierTracer());
  TraceEdgeForBarrier(gcmarker, cell, cell->getTraceKind());
}

void js::gc::PerformIncrementalPreWriteBarrier(TenuredCell* cell) {
  // The same as PerformIncrementalReadBarrier except for an extra check on the
  // runtime for cells in atoms zone.

  MOZ_ASSERT(cell);
  if (cell->isMarkedBlack()) {
    return;
  }

  // Barriers can be triggered off the main thread by background finalization of
  // HeapPtrs to the atoms zone. We don't want to trigger the barrier in this
  // case.
  Zone* zone = cell->zoneFromAnyThread();
  bool checkThread = zone->isAtomsZone();
  JSRuntime* runtime = cell->runtimeFromAnyThread();
  if (checkThread && !CurrentThreadCanAccessRuntime(runtime)) {
    MOZ_ASSERT(CurrentThreadIsGCFinalizing());
    return;
  }

  MOZ_ASSERT(zone->needsMarkingBarrier());
  MOZ_ASSERT(CurrentThreadIsMainThread());
  MOZ_ASSERT(!JS::RuntimeHeapIsMajorCollecting());

  // Skip dispatching on known tracer type.
  GCMarker* gcmarker = GCMarker::fromTracer(zone->barrierTracer());
  TraceEdgeForBarrier(gcmarker, cell, cell->getTraceKind());
}

#ifdef ENABLE_WASM_JSPI
void js::gc::PerformIncrementalPreWriteBarrierAllChildren(JSObject* cell) {
  if (!cell) {
    return;
  }

  // If the object is already marked black, its children may already be in the
  // GC's marking work queue. However, with incremental and concurrent marking,
  // objects can be marked black before their trace hooks have run. So we
  // conservatively mark it even if it's black.
  Zone* zone = cell->zoneFromAnyThread();
  MOZ_ASSERT(!zone->isAtomsZone());
  MOZ_ASSERT(zone->needsMarkingBarrier());
  MOZ_ASSERT(CurrentThreadIsMainThread());
  MOZ_ASSERT(!JS::RuntimeHeapIsMajorCollecting());

  // Skip dispatching on known tracer type.
  GCMarker* gcmarker = GCMarker::fromTracer(zone->barrierTracer());

  MOZ_ASSERT(ShouldMark(gcmarker->markColor(), cell));
  CheckTracedThing(gcmarker->tracer(), cell);
  AutoClearTracingSource acts(gcmarker->tracer());
#  ifdef DEBUG
  AutoSetThreadIsMarking threadIsMarking;
#  endif  // DEBUG
  cell->traceChildren(zone->barrierTracer());
}
#endif  // ENABLE_WASM_JSPI

void js::gc::PerformIncrementalBarrierDuringFlattening(JSString* str) {
  TenuredCell* cell = &str->asTenured();

  // Skip eager marking of ropes during flattening. Their children will also be
  // barriered by flattening process so we don't need to traverse them.
  if (str->isRope()) {
    cell->markBlack();
    return;
  }

  PerformIncrementalPreWriteBarrier(cell);
}

template <uint32_t opts>
template <typename T>
void MarkingTracerT<opts>::markAndTraverse(T* thing) {
  if (!mark(thing)) {
    return;
  }

  // We only mark permanent things during initialization.
  MOZ_ASSERT_IF(thing->isPermanentAndMayBeShared(),
                !this->runtime()->permanentAtomsPopulated());

  MemoryAcquireFence<opts>(this->runtime());

  traverse(thing);
}

// The |traverse| method overloads select the traversal strategy for each kind.
//
// There are three possible strategies:
//
// 1. traceChildren
//
//    The simplest traversal calls out to the fully generic traceChildren
//    function to visit the child edges. In the absence of other traversal
//    mechanisms, this function will rapidly grow the stack past its bounds and
//    crash the process. Thus, this generic tracing should only be used in cases
//    where subsequent tracing will not recurse.
//
// 2. scanChildren
//
//    Strings, Shapes, and Scopes are extremely common, but have simple patterns
//    of recursion. We traverse trees of these edges immediately, with
//    aggressive, manual inlining, implemented by eagerlyTraceChildren.
//
// 3. pushThing
//
//    Objects are extremely common and can contain arbitrarily nested graphs, so
//    are not trivially inlined. In this case we use the mark stack to control
//    recursion. JitCode shares none of these properties, but is included for
//    historical reasons. JSScript normally cannot recurse, but may be used as a
//    weakmap key and thereby recurse into weakmapped values.

template <uint32_t opts>
void MarkingTracerT<opts>::traverse(GetterSetter* thing) {
  traceChildren(thing);
}
template <uint32_t opts>
void MarkingTracerT<opts>::traverse(JS::Symbol* thing) {
  if constexpr (hasOption(MarkingOptions::MarkImplicitEdges)) {
    pushThing(thing);
    return;
  }
  traceChildren(thing);
}
template <uint32_t opts>
void MarkingTracerT<opts>::traverse(JS::BigInt* thing) {
  traceChildren(thing);
}
template <uint32_t opts>
void MarkingTracerT<opts>::traverse(RegExpShared* thing) {
  traceChildren(thing);
}
template <uint32_t opts>
void MarkingTracerT<opts>::traverse(JSString* thing) {
  scanChildren(thing);
}
template <uint32_t opts>
void MarkingTracerT<opts>::traverse(Shape* thing) {
  scanChildren(thing);
}
template <uint32_t opts>
void MarkingTracerT<opts>::traverse(BaseShape* thing) {
  scanChildren(thing);
}
template <uint32_t opts>
void MarkingTracerT<opts>::traverse(PropMap* thing) {
  scanChildren(thing);
}
template <uint32_t opts>
void MarkingTracerT<opts>::traverse(js::Scope* thing) {
  scanChildren(thing);
}
template <uint32_t opts>
void MarkingTracerT<opts>::traverse(JSObject* thing) {
  pushThing(thing);
}
template <uint32_t opts>
void MarkingTracerT<opts>::traverse(jit::JitCode* thing) {
  pushThing(thing);
}
template <uint32_t opts>
void MarkingTracerT<opts>::traverse(BaseScript* thing) {
  pushThing(thing);
}

template <uint32_t opts>
template <typename T>
void MarkingTracerT<opts>::traceChildren(T* thing) {
  MOZ_ASSERT(!thing->isPermanentAndMayBeShared());
  MOZ_ASSERT(thing->isMarkedAny());
  AutoSetTracingSource asts(this, thing);
  thing->traceChildren(this);
}

template <uint32_t opts>
template <typename T>
void MarkingTracerT<opts>::scanChildren(T* thing) {
  MOZ_ASSERT(!thing->isPermanentAndMayBeShared());
  MOZ_ASSERT(thing->isMarkedAny());
  eagerlyMarkChildren(thing);
}

template <uint32_t opts>
template <typename T>
void MarkingTracerT<opts>::pushThing(T* thing) {
  MOZ_ASSERT(!thing->isPermanentAndMayBeShared());
  MOZ_ASSERT(thing->isMarkedAny());
  gcMarker()->pushTaggedPtr(thing);
}

template void MarkingTracerT<MarkingOptions::None>::markAndTraverse(
    JSObject* thing);
template void MarkingTracerT<
    MarkingOptions::MarkImplicitEdges>::markAndTraverse(JSObject* thing);
template void MarkingTracerT<
    MarkingOptions::MarkRootCompartments>::markAndTraverse(JSObject* thing);

#ifdef DEBUG
void GCMarker::setCheckAtomMarking(bool check) {
  MOZ_ASSERT(check != checkAtomMarking);
  checkAtomMarking = check;
}
#endif

template <typename S, typename T>
inline void GCMarker::checkTraversedEdge(S source, T* target) {
#ifdef DEBUG
  // Atoms and Symbols do not have or mark their internal pointers,
  // respectively.
  MOZ_ASSERT(!source->isPermanentAndMayBeShared());

  // Shared things are already black so we will not mark them.
  if (target->isPermanentAndMayBeShared()) {
    Zone* zone = target->zoneFromAnyThread();
    MOZ_ASSERT(!zone->wasGCStarted());
    MOZ_ASSERT(!zone->needsMarkingBarrier());
    MOZ_ASSERT(target->isMarkedBlack());
    MOZ_ASSERT(!target->maybeCompartment());
    return;
  }

  Zone* sourceZone = source->zone();
  Zone* targetZone = target->zone();

  // Atoms and Symbols do not have access to a compartment pointer, or we'd need
  // to adjust the subsequent check to catch that case.
  MOZ_ASSERT_IF(targetZone->isAtomsZone(), !target->maybeCompartment());

  // The Zones must match, unless the target is an atom.
  MOZ_ASSERT(targetZone == sourceZone || targetZone->isAtomsZone());

  // If we are marking an atom, that atom must be marked in the source zone's
  // atom bitmap.
  if (checkAtomMarking && !sourceZone->isAtomsZone() &&
      targetZone->isAtomsZone()) {
    GCRuntime* gc = &target->runtimeFromAnyThread()->gc;
    TenuredCell* atom = &target->asTenured();
    MOZ_ASSERT(gc->atomMarking.getAtomMarkColor(sourceZone, atom) >=
               AsCellColor(markColor()));
  }

  // If we have access to a compartment pointer for both things, they must
  // match.
  MOZ_ASSERT_IF(source->maybeCompartment() && target->maybeCompartment(),
                source->maybeCompartment() == target->maybeCompartment());
#endif
}

template <uint32_t opts>
template <typename S, typename T>
void MarkingTracerT<opts>::markAndTraverseEdge(S* source, T* target) {
  if constexpr (std::is_same_v<T, JS::Symbol>) {
    if (markColor() == MarkColor::Black) {
      MaybeUnmarkGraySymbol(this->runtime(), source->zone(), target);
    }
  }

  gcMarker()->checkTraversedEdge(source, target);
  markAndTraverse(target);
}

template <uint32_t opts>
template <typename S, typename T>
void MarkingTracerT<opts>::markAndTraverseEdge(S* source, const T& target) {
  ApplyGCThingTyped(
      target, [this, source](auto t) { this->markAndTraverseEdge(source, t); });
}

template <uint32_t opts>
MOZ_NEVER_INLINE bool MarkingTracerT<opts>::markAndTraversePrivateGCThing(
    JSObject* source, Cell* target) {
  JS::TraceKind kind = target->getTraceKind();
  ApplyGCThingTyped(target, kind, [this, source](auto t) {
    this->markAndTraverseEdge(source, t);
  });

  GCMarker* marker = gcMarker();
  // Ensure stack headroom in case we pushed.
  if (MOZ_UNLIKELY(!marker->stack.ensureSpace(ValueRangeWords))) {
    marker->delayMarkingChildrenOnOOM(source);
    return false;
  }

  return true;
}

template <uint32_t opts>
bool MarkingTracerT<opts>::markAndTraverseSymbol(JSObject* source,
                                                 JS::Symbol* target) {
  this->markAndTraverseEdge(source, target);

  GCMarker* marker = gcMarker();
  // Ensure stack headroom in case we pushed.
  if (MOZ_UNLIKELY(!marker->stack.ensureSpace(ValueRangeWords))) {
    marker->delayMarkingChildrenOnOOM(source);
    return false;
  }

  return true;
}

template <uint32_t opts>
template <typename T>
bool MarkingTracerT<opts>::mark(T* thing) {
  if (!thing->isTenured()) {
    return false;
  }

  if constexpr (std::is_same_v<T, JS::Symbol>) {
    // Don't mark symbols owned by other runtimes. Mark symbols black in
    // uncollected zones for gray unmarking, but don't mark symbols gray in
    // uncollected zones.
    if (IsOwnedByOtherRuntime(this->runtime(), thing) ||
        (markColor() == MarkColor::Gray &&
         !thing->zone()->isGCMarkingOrVerifyingPreBarriers())) {
      return false;
    }
  }

  AssertShouldMarkInZone(gcMarker(), thing);

  MarkColor color =
      TraceKindCanBeGray<T>::value ? markColor() : MarkColor::Black;

#ifdef JS_GC_CONCURRENT_MARKING
  // TODO: We don't need thread safe marking if concurrent marking is
  // disabled. We do need this for barrier tracing on the main thread during
  // concurrent marking however.
  return thing->asTenured().markIfUnmarkedThreadSafe(color);
#else
  if constexpr (hasOption(MarkingOptions::AtomicMarking)) {
    return thing->asTenured().markIfUnmarkedThreadSafe(color);
  }

  return thing->asTenured().markIfUnmarked(color);
#endif
}

/*** Mark-stack Marking *****************************************************/

static gcstats::PhaseKind GrayMarkingPhaseForCurrentPhase(
    const gcstats::Statistics& stats) {
  using namespace gcstats;

  MOZ_ASSERT(CurrentThreadIsMainThread());

  switch (stats.currentPhaseKind()) {
    case PhaseKind::MARK:
      return PhaseKind::MARK_GRAY;
    case PhaseKind::MARK_WEAK:
      return PhaseKind::MARK_GRAY_WEAK;
    default:
      MOZ_CRASH("Unexpected current phase");
  }
}

/* static */
void GCMarker::moveAllWork(GCMarker* dst, GCMarker* src) {
  MOZ_ASSERT(dst->markColor() == src->markColor());
  MarkStack::moveAllWork(dst->stack, src->stack);
  MarkStack::moveAllWork(dst->otherStack, src->otherStack);
}

/* static */
size_t GCMarker::moveSomeWork(GCMarker* dst, GCMarker* src,
                              bool allowDistribute) {
  MOZ_ASSERT(dst->markColor() == src->markColor());
  MOZ_ASSERT(dst->stack.isEmpty());
  MOZ_ASSERT(src->canDonateWork());

  return MarkStack::moveSomeWork(src, dst->stack, src->stack, allowDistribute);
}

bool GCMarker::initStack() {
  MOZ_ASSERT(!isActive());
  MOZ_ASSERT(markColor_ == gc::MarkColor::Black);
  return stack.init();
}

void GCMarker::resetStackCapacity() {
  MOZ_ASSERT(!isActive());
  MOZ_ASSERT(markColor_ == gc::MarkColor::Black);
  (void)stack.resetStackCapacity();
}

void GCMarker::freeStack() {
  MOZ_ASSERT(!isActive());
  MOZ_ASSERT(markColor_ == gc::MarkColor::Black);
  stack.clearAndFreeStack();
}

bool GCMarker::markUntilBudgetExhausted(SliceBudget& budget,
                                        ShouldReportMarkTime reportTime) {
  MOZ_ASSERT(isRegularMarking() || isWeakMarking() || isConcurrentMarking());

#ifdef DEBUG
  MOZ_ASSERT(!strictCompartmentChecking);
  strictCompartmentChecking = true;
  auto acc = mozilla::MakeScopeExit([&] { strictCompartmentChecking = false; });
#endif

  if (budget.isOverBudget()) {
    return false;
  }

  return matchTracer(
      [&](auto& trc) { return trc.doMarking(budget, reportTime); });
}

template <uint32_t opts>
bool MarkingTracerT<opts>::doMarking(SliceBudget& budget,
                                     ShouldReportMarkTime reportTime) {
  GCMarker* marker = gcMarker();
  GCRuntime& gc = this->runtime()->gc;

  // This method leaves the mark color as it found it.

  if (marker->hasBlackEntries() || gc.hasDeferredWeakMaps(MarkColor::Black)) {
    if (!markOneColor<MarkColor::Black>(budget)) {
      return false;
    }
  }

  if (marker->hasGrayEntries() || gc.hasDeferredWeakMaps(MarkColor::Gray)) {
    mozilla::Maybe<gcstats::AutoPhase> ap;
    if (reportTime) {
      auto& stats = this->runtime()->gc.stats();
      ap.emplace(stats, GrayMarkingPhaseForCurrentPhase(stats));
    }

    if (!markOneColor<MarkColor::Gray>(budget)) {
      return false;
    }
  }

  // Mark children of things that caused too deep recursion during the above
  // tracing. All normal marking happens before any delayed marking.
  if (marker == &gc.marker() && gc.hasDelayedMarking()) {
    gc.markAllDelayedChildren(reportTime);
    MOZ_ASSERT(!gc.hasDelayedMarking());
  }

  MOZ_ASSERT(marker->isMarkStackEmpty());

  return true;
}

template <uint32_t opts>
template <MarkColor color>
bool MarkingTracerT<opts>::markOneColor(SliceBudget& budget) {
  GCMarker* marker = gcMarker();
  AutoSetMarkColor setColor(*marker, color);
  AutoUpdateMarkStackRanges updateRanges(*marker);
  return markCurrentColor(budget);
}

template <uint32_t opts>
bool MarkingTracerT<opts>::markCurrentColor(SliceBudget& budget) {
  GCMarker* marker = gcMarker();
  while (true) {
    if (marker->hasEntriesForCurrentColor()) {
      if (!processMarkStackTop(budget)) {
        return false;
      }
    } else {
      marker->markDeferredWeakMapChildren(
          marker->runtime()->gc.deferredMapsList(marker->markColor()));
      if (!marker->hasEntriesForCurrentColor()) {
        return true;
      }
    }
  }
}

void GCMarker::markDeferredWeakMapChildren(WeakMapList& deferred) {
  // Even if this is called while parallel marking, there will only be one
  // thread running at this time.
  enterSingleThreadedMode();
  while (js::WeakMapBase* map = deferred.popFirst()) {
    (void)map->markEntries(this);
    MOZ_ASSERT(!map->isSystem());
    map->zone()->gcMarkedUserWeakMaps().pushBack(map);
  }
  leaveSingleThreadedMode();
}

bool GCMarker::markCurrentColorInParallel(ParallelMarkTask* task,
                                          SliceBudget& budget) {
  MOZ_ASSERT(isParallelMarking());
  MOZ_ASSERT(stack.elementsRangesAreValid);

  ParallelMarkTask::AtomicCount& waitingTaskCount = task->waitingTaskCountRef();

  auto* trc = &tracer_.as<ParallelMarkingTracer>();
  while (trc->processMarkStackTop(budget)) {
    if (stack.isEmpty()) {
      return true;
    }

    // TODO: It might be better to only check this occasionally, possibly
    // combined with the slice budget check. Experiments with giving this its
    // own counter resulted in worse performance.
    if (waitingTaskCount && shouldDonateWork()) {
      task->donateWork();
    }
  }

  return false;
}

#ifdef DEBUG
void GCMarker::markOneObjectForTest(JSObject* obj) {
  MOZ_ASSERT(this == &runtime()->gc.marker());
  MOZ_ASSERT(obj->zone()->isGCMarking());
  MOZ_ASSERT(!obj->isMarked(markColor()));

  // Mark the object and put it on the stack for traversal. Note that if obj is
  // a weakmap, it will be added to the deferred maps list instead.
  matchTracer([this, obj](auto& trc) {
    size_t oldPosition = stack.position();
    trc.markAndTraverse(obj);
    MOZ_ASSERT(obj->isMarked(markColor()));
    if (stack.position() == oldPosition) {
      return;
    }

    // Process the top of the mark stack, ie the object that was just pushed.
    AutoUpdateMarkStackRanges updateRanges(*this);
    SliceBudget unlimited = SliceBudget::unlimited();
    trc.processMarkStackTop(unlimited);
  });
}
#endif

#ifdef JS_GC_CONCURRENT_MARKING

// The maximum number of entries in a main thread buffer before we stop
// concurrent marking and interrupt the main thread to do this work.
static constexpr size_t MainThreadBufferThreshold = 16384;

inline bool GCMarker::addToMainThreadBuffer(JSObject* object,
                                            SliceBudget& budget) {
  auto& buffer = markColor() == MarkColor::Black ? blackMainThreadBuffer_.ref()
                                                 : grayMainThreadBuffer_.ref();
  if (!buffer.append(object)) {
    return false;
  }

  if (MOZ_UNLIKELY(buffer.length() == MainThreadBufferThreshold)) {
    // Ensure |budget.isOverBudget()| will return true if the buffer is full.
    budget.setInterrupted();
    budget.forceCheck();
  }

  return true;
}

bool GCMarker::processMainThreadBuffers(SliceBudget& budget) {
  // We can be on the main thread or on a helper thread during sweeping here.
  MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime()) ||
             JS::RuntimeHeapIsMajorCollecting());

  MOZ_ASSERT(markColor() == MarkColor::Black);
  if (!processMainThreadBuffer(blackMainThreadBuffer_.ref(), budget)) {
    return false;
  }

  if (!grayMainThreadBuffer_.ref().empty()) {
    // Allow pushing gray marking even if there is still black marking
    // work. This reduces the amount of handshaking between the main thread and
    // the marking thread.
    AutoSetMarkColor autoSetGray(*this, MarkColor::Gray,
                                 AllowGrayMarkingBeforeEndOfBlackMarking::Yes);
    if (!processMainThreadBuffer(grayMainThreadBuffer_.ref(), budget)) {
      return false;
    }
  }

  MOZ_ASSERT(mainThreadBuffersAreEmpty());

  return true;
}

bool GCMarker::processMainThreadBuffer(MainThreadBuffer& buffer,
                                       SliceBudget& budget) {
  while (!buffer.empty()) {
    JSObject* obj = buffer.popCopy();

    MOZ_ASSERT(obj->isMarkedAtLeast(markColor()));
    if (markColor() == MarkColor::Gray && obj->isMarkedBlack()) {
      // We subsequently marked this black so we can skip marking it gray.
      continue;
    }

    const JSClass* clasp = obj->getClass();
    MOZ_ASSERT(clasp->hasTrace());
    AutoSetTracingSource asts(tracer(), obj);
    clasp->doTrace(tracer(), obj);

    budget.step();
    if (budget.isOverBudget()) {
      return false;
    }
  }

  return true;
}

#endif  // JS_GC_CONCURRENT_MARKING

static inline void CheckForCompartmentMismatch(JSObject* obj, JSObject* obj2) {
#ifdef DEBUG
  if (MOZ_UNLIKELY(obj->compartment() != obj2->compartment())) {
    fprintf(
        stderr,
        "Compartment mismatch in pointer from %s object slot to %s object\n",
        obj->getClass()->name, obj2->getClass()->name);
    MOZ_CRASH("Compartment mismatch");
  }
#endif
}

static inline size_t NumUsedFixedSlots(NativeObject* obj) {
  // Concurrent marking: this can happen concurrently with a shape change by the
  // mutator. This is safe because 1) the total number of fixed slots cannot
  // change and 2) if the slot span changes new/deleted slots still get marked
  // because of the snapshot at the beginning invariant. We do need to ensure we
  // only read object fields once though.
  Shape* shape = obj->shape();
  ObjectSlots* slotsHeader = obj->getSlotsHeader();
  return std::min(NumNativeObjectFixedSlots(shape),
                  NativeObjectSlotSpan(shape, slotsHeader));
}

#ifndef JS_GC_CONCURRENT_MARKING
static inline size_t NumUsedDynamicSlots(NativeObject* obj) {
  size_t nfixed = obj->numFixedSlots();
  size_t nslots = obj->slotSpan();
  if (nslots < nfixed) {
    return 0;
  }

  return nslots - nfixed;
}
#endif

void GCMarker::updateRangesAtStartOfSlice() {
  MOZ_ASSERT(!stack.elementsRangesAreValid);

  for (MarkStackIter iter(stack); !iter.done(); iter.next()) {
    if (iter.isSlotsOrElementsRange()) {
      MarkStack::SlotsOrElementsRange range = iter.slotsOrElementsRange();
      JSObject* obj = range.ptr().asRangeObject();
      MOZ_ASSERT(obj->is<NativeObject>());
      if (range.kind() == SlotsOrElementsKind::Elements) {
        NativeObject* nobj = &obj->as<NativeObject>();
        size_t index = range.start();
        size_t numShifted = nobj->getElementsHeader()->numShiftedElements();
        index -= std::min(numShifted, index);
        range.setStart(index);
        iter.setSlotsOrElementsRange(range);
      }
    }
  }

#ifdef DEBUG
  stack.elementsRangesAreValid = true;
#endif
}

void GCMarker::updateRangesAtEndOfSlice() {
  MOZ_ASSERT(stack.elementsRangesAreValid);

  for (MarkStackIter iter(stack); !iter.done(); iter.next()) {
    if (iter.isSlotsOrElementsRange()) {
      MarkStack::SlotsOrElementsRange range = iter.slotsOrElementsRange();
      if (range.kind() == SlotsOrElementsKind::Elements) {
        NativeObject* obj = &range.ptr().asRangeObject()->as<NativeObject>();
        size_t numShifted = obj->getElementsHeader()->numShiftedElements();
        range.setStart(range.start() + numShifted);
        iter.setSlotsOrElementsRange(range);
      }
    }
  }

#ifdef DEBUG
  stack.elementsRangesAreValid = false;
#endif
}

template <uint32_t opts>
inline bool MarkingTracerT<opts>::processMarkStackTop(SliceBudget& budget) {
  /*
   * This function uses explicit goto and scans objects directly. This allows us
   * to eliminate tail recursion and significantly improve the marking
   * performance, see bug 641025.
   *
   * Note that the mutator can change the size and layout of objects between
   * marking slices, so we must check slots and element ranges read from the
   * stack.
   */


  GCMarker* marker = gcMarker();
  MarkStack& stack = marker->stack;

  MOZ_ASSERT(!stack.isEmpty());
  MOZ_ASSERT(stack.elementsRangesAreValid);
  MOZ_ASSERT_IF(markColor() == MarkColor::Gray, !marker->hasBlackEntries());

  JSObject* obj;             // The object being scanned.
  SlotsOrElementsKind kind;  // The kind of slot range being scanned, if any.
  HeapSlot* base;            // Slot range base pointer.
  size_t index;              // Index of the next slot to mark.
  size_t end;                // End of slot range to mark.

  if (stack.peekTag() == MarkStack::SlotsOrElementsRangeTag) {
    auto range = stack.popSlotsOrElementsRange();
    obj = range.ptr().asRangeObject();
    NativeObject* nobj = &obj->as<NativeObject>();
    kind = range.kind();
    index = range.start();

    switch (kind) {
      case SlotsOrElementsKind::FixedSlots: {
        base = nobj->fixedSlots();
        end = NumUsedFixedSlots(nobj);
        break;
      }

      case SlotsOrElementsKind::DynamicSlots: {
        base = nobj->slots_;
#ifdef JS_GC_CONCURRENT_MARKING
        // TODO: Investigate whether we can safely restrict this to the number
        // of used slots.
        end = ObjectSlots::fromSlots(base)->capacity();
#else
        end = NumUsedDynamicSlots(nobj);
#endif
        break;
      }

      case SlotsOrElementsKind::Elements: {
        base = nobj->getDenseElements();
        end = nobj->getDenseInitializedLength();
        break;
      }

      case SlotsOrElementsKind::Unused: {
        MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unused SlotsOrElementsKind");
      }
    }

    goto scan_value_range;
  }

  budget.step();
  if (budget.isOverBudget()) {
    return false;
  }

  {
    MarkStack::TaggedPtr ptr = stack.popPtr();
    switch (ptr.tag()) {
      case MarkStack::ObjectTag: {
        obj = ptr.as<JSObject>();
        AssertShouldMarkInZone(marker, obj);
        goto scan_obj;
      }

      case MarkStack::SymbolTag: {
        auto* symbol = ptr.as<JS::Symbol>();
        maybeMarkImplicitEdges(symbol);
        AutoSetTracingSource asts(this, symbol);
        symbol->traceChildren(this);
        return true;
      }

      case MarkStack::JitCodeTag: {
        auto* code = ptr.as<jit::JitCode>();
        AutoSetTracingSource asts(this, code);
        code->traceChildren(this);
        return true;
      }

      case MarkStack::ScriptTag: {
        auto* script = ptr.as<BaseScript>();
        maybeMarkImplicitEdges(script);
        AutoSetTracingSource asts(this, script);
        script->traceChildren(this);
        return true;
      }

      default:
        MOZ_CRASH("Invalid tag in mark stack");
    }
  }

  return true;

scan_value_range:
  MemoryAcquireFence<opts>(this->runtime());

  while (index < end) {
    MOZ_ASSERT(stack.capacity() >= stack.position() + ValueRangeWords);

    budget.step();
    if (budget.isOverBudget()) {
      marker->pushValueRange(obj, kind, index, end);
      return false;
    }

    Value v = base[index];
    index++;

    if (!v.isGCThing()) {
      continue;
    }

    if (v.isString()) {
      markAndTraverseEdge(obj, v.toString());
    } else if (v.isObject()) {
      JSObject* obj2 = &v.toObject();
#ifdef DEBUG
      if (!obj2) {
        fprintf(stderr,
                "processMarkStackTop found ObjectValue(nullptr) "
                "at %zu Values from end of range in object:\n",
                size_t(end - (index - 1)));
        obj->dump();
      }
#endif
      CheckForCompartmentMismatch(obj, obj2);
      if (mark(obj2)) {
        // Save the rest of this value range for later and start scanning obj2's
        // children.
        marker->pushValueRange(obj, kind, index, end);
        obj = obj2;
        goto scan_obj;
      }
    } else if (v.isSymbol()) {
      if (!markAndTraverseSymbol(obj, v.toSymbol())) {
        return true;
      }
    } else if (v.isBigInt()) {
      markAndTraverseEdge(obj, v.toBigInt());
    } else {
      MOZ_ASSERT(v.isPrivateGCThing());
      if (!markAndTraversePrivateGCThing(obj, v.toGCThing())) {
        return true;
      }
    }
  }

  return true;

scan_obj: {
  AssertShouldMarkInZone(marker, obj);

  maybeMarkImplicitEdges(obj);
  markAndTraverseEdge(obj, obj->shape());

  const JSClass* clasp = obj->getClass();
  if (clasp->hasTrace() && !callOrDelayTraceHook(obj, clasp, budget)) {
    return false;
  }

  if (!obj->is<NativeObject>()) {
    return true;
  }

  // Ensure stack headroom for three ranges (fixed slots, dynamic slots and
  // elements).
  if (MOZ_UNLIKELY(!stack.ensureSpace(ValueRangeWords * 3))) {
    marker->delayMarkingChildrenOnOOM(obj);
    return true;
  }

  // For concurrent marking, we need to read all object fields at most once to
  // prevent the possibility of seeing different values each time.
  NativeObject* nobj = &obj->as<NativeObject>();
  Shape* shape = nobj->shape();
  HeapSlot* slotsPtr = nobj->slots_;
  HeapSlot* elementsPtr = nobj->elements_;

  // Get number of slots using previously read shape and slots pointers.
  ObjectSlots* slotsHeader = ObjectSlots::fromSlots(slotsPtr);
  unsigned nslots = NativeObjectSlotSpan(shape, slotsHeader);
  unsigned nfixed = NumNativeObjectFixedSlots(shape);

  if (IsNativeObjectDynamicSlots(slotsPtr)) {
    MarkTenuredBuffer(nobj->zone(), slotsHeader);
  }

  ObjectElements* elementsHeader = ObjectElements::fromElements(elementsPtr);
  if (IsNativeObjectDynamicElements(elementsPtr)) {
    void* unshiftedHeader = elementsHeader->getUnshiftedHeader();
    MarkTenuredBuffer(nobj->zone(), unshiftedHeader);
  }

  if (!IsNativeObjectEmptyElements(elementsPtr)) {
    base = elementsPtr;
    kind = SlotsOrElementsKind::Elements;
    index = 0;
    end = elementsHeader->getInitializedLength();

    if (!nslots) {
      // No slots at all. Scan elements immediately.
      goto scan_value_range;
    }

    marker->pushValueRange(nobj, kind, index, end);
  }

  base = nobj->fixedSlots();
  kind = SlotsOrElementsKind::FixedSlots;
  index = 0;

  if (nslots > nfixed) {
    // Push dynamic slots for later scan.
    marker->pushValueRange(nobj, SlotsOrElementsKind::DynamicSlots, 0,
                           nslots - nfixed);
    end = nfixed;
  } else {
    end = nslots;
  }

  // Scan any fixed slots.
  goto scan_value_range;
}
}

template <uint32_t opts>
bool MarkingTracerT<opts>::callOrDelayTraceHook(JSObject* obj,
                                                const JSClass* clasp,
                                                JS::SliceBudget& budget) {
  MOZ_ASSERT(clasp->hasTrace());

#ifdef JS_GC_CONCURRENT_MARKING
  if constexpr (hasOption(MarkingOptions::ConcurrentMarking)) {
    // TODO: Add a class flag to allow us to call the trace hook concurrently
    // for classes that support it.
    GCMarker* marker = gcMarker();
    if (MOZ_UNLIKELY(!marker->addToMainThreadBuffer(obj, budget))) {
      marker->delayMarkingChildrenOnOOM(obj);
      return false;
    }
    return true;
  }
#endif

  AutoSetTracingSource asts(this, obj);
  clasp->doTrace(this, obj);
  return true;
}

/*** Mark Stack *************************************************************/

static_assert(sizeof(MarkStack::TaggedPtr) == sizeof(uintptr_t),
              "A TaggedPtr should be the same size as a pointer");
static_assert((sizeof(MarkStack::SlotsOrElementsRange) % sizeof(uintptr_t)) ==
                  0,
              "SlotsOrElementsRange size should be a multiple of "
              "the pointer size");

template <typename T>
struct MapTypeToMarkStackTag {};
template <>
struct MapTypeToMarkStackTag<JSObject*> {
  static const auto value = MarkStack::ObjectTag;
};
template <>
struct MapTypeToMarkStackTag<JS::Symbol*> {
  static const auto value = MarkStack::SymbolTag;
};
template <>
struct MapTypeToMarkStackTag<jit::JitCode*> {
  static const auto value = MarkStack::JitCodeTag;
};
template <>
struct MapTypeToMarkStackTag<BaseScript*> {
  static const auto value = MarkStack::ScriptTag;
};

static inline bool TagIsRangeTag(MarkStack::Tag tag) {
  return tag == MarkStack::SlotsOrElementsRangeTag;
}

inline MarkStack::TaggedPtr::TaggedPtr(Tag tag, Cell* ptr)
    : bits(tag | uintptr_t(ptr)) {
  assertValid();
}

/* static */
inline MarkStack::TaggedPtr MarkStack::TaggedPtr::fromBits(uintptr_t bits) {
  return TaggedPtr(bits);
}

inline MarkStack::TaggedPtr::TaggedPtr(uintptr_t bits) : bits(bits) {
  assertValid();
}

inline uintptr_t MarkStack::TaggedPtr::asBits() const { return bits; }

inline MarkStack::Tag MarkStack::TaggedPtr::tag() const {
  auto tag = Tag(bits & TagMask);
  MOZ_ASSERT(tag <= LastTag);
  return tag;
}

inline Cell* MarkStack::TaggedPtr::ptr() const {
  return reinterpret_cast<Cell*>(bits & ~TagMask);
}

inline void MarkStack::TaggedPtr::assertValid() const {
  (void)tag();
  MOZ_ASSERT(IsCellPointerValid(ptr()));
}

template <typename T>
inline T* MarkStack::TaggedPtr::as() const {
  MOZ_ASSERT(tag() == MapTypeToMarkStackTag<T*>::value);
  MOZ_ASSERT(ptr()->isTenured());
  MOZ_ASSERT(ptr()->is<T>());
  return static_cast<T*>(ptr());
}

inline JSObject* MarkStack::TaggedPtr::asRangeObject() const {
  MOZ_ASSERT(TagIsRangeTag(tag()));
  MOZ_ASSERT(ptr()->isTenured());
  return ptr()->as<JSObject>();
}

inline JSRope* MarkStack::TaggedPtr::asTempRope() const {
  MOZ_ASSERT(tag() == TempRopeTag);
  return &ptr()->as<JSString>()->asRope();
}

inline MarkStack::SlotsOrElementsRange::SlotsOrElementsRange(
    SlotsOrElementsKind kindArg, JSObject* obj, size_t startArg)
    : startAndKind_((startArg << StartShift) | size_t(kindArg)),
      ptr_(SlotsOrElementsRangeTag, obj) {
  assertValid();
  MOZ_ASSERT(kind() == kindArg);
  MOZ_ASSERT(start() == startArg);
}

/* static */
inline MarkStack::SlotsOrElementsRange
MarkStack::SlotsOrElementsRange::fromBits(uintptr_t startAndKind,
                                          uintptr_t ptr) {
  return SlotsOrElementsRange(startAndKind, ptr);
}

inline MarkStack::SlotsOrElementsRange::SlotsOrElementsRange(
    uintptr_t startAndKind, uintptr_t ptr)
    : startAndKind_(startAndKind), ptr_(TaggedPtr::fromBits(ptr)) {
  assertValid();
}

inline void MarkStack::SlotsOrElementsRange::assertValid() const {
  ptr_.assertValid();
  MOZ_ASSERT(TagIsRangeTag(ptr_.tag()));
}

inline SlotsOrElementsKind MarkStack::SlotsOrElementsRange::kind() const {
  return SlotsOrElementsKind(startAndKind_ & KindMask);
}

inline size_t MarkStack::SlotsOrElementsRange::start() const {
  return startAndKind_ >> StartShift;
}

inline void MarkStack::SlotsOrElementsRange::setStart(size_t newStart) {
  startAndKind_ = (newStart << StartShift) | uintptr_t(kind());
  MOZ_ASSERT(start() == newStart);
}

inline void MarkStack::SlotsOrElementsRange::setEmpty() {
  // Replace this SlotsOrElementsRange with something that's valid for marking
  // but doesn't involve accessing this range, which is now invalid. This
  // replaces the two-word range with two single-word entries for the owning
  // object.
  TaggedPtr entry(ObjectTag, ptr().asRangeObject());
  ptr_ = entry;
  startAndKind_ = entry.asBits();
}

inline MarkStack::TaggedPtr MarkStack::SlotsOrElementsRange::ptr() const {
  return ptr_;
}

inline uintptr_t MarkStack::SlotsOrElementsRange::asBits0() const {
  return startAndKind_;
}

inline uintptr_t MarkStack::SlotsOrElementsRange::asBits1() const {
  return ptr_.asBits();
}

MarkStack::MarkStack() { MOZ_ASSERT(isEmpty()); }

MarkStack::~MarkStack() {
  MOZ_ASSERT(isEmpty());
  clearAndFreeStack();
}

void MarkStack::swap(MarkStack& other) {
  std::swap(stack_, other.stack_);
  std::swap(capacity_, other.capacity_);
  std::swap(topIndex_, other.topIndex_);
#ifdef JS_GC_ZEAL
  std::swap(maxCapacity_, other.maxCapacity_);
#endif
#ifdef DEBUG
  std::swap(elementsRangesAreValid, other.elementsRangesAreValid);
#endif
}

bool MarkStack::init() { return resetStackCapacity(); }

bool MarkStack::resetStackCapacity() {
  MOZ_ASSERT(isEmpty());

  size_t capacity = MARK_STACK_BASE_CAPACITY;

#ifdef JS_GC_ZEAL
  capacity = std::min(capacity, maxCapacity_.ref());
#endif

  return resize(capacity);
}

#ifdef JS_GC_ZEAL
void MarkStack::setMaxCapacity(size_t maxCapacity) {
  MOZ_ASSERT(maxCapacity != 0);
  MOZ_ASSERT(isEmpty());

  maxCapacity_ = maxCapacity;
  if (capacity() > maxCapacity_) {
    // If the realloc fails, just keep using the existing stack; it's
    // not ideal but better than failing.
    (void)resize(maxCapacity_);
  }
}
#endif

MOZ_ALWAYS_INLINE bool MarkStack::indexIsEntryBase(size_t index) const {
  // The mark stack holds both TaggedPtr and SlotsOrElementsRange entries, which
  // are one or two words long respectively. Determine whether |index| points to
  // the base of an entry (i.e. the lowest word in memory).
  //
  // The possible cases are that |index| points to:
  //  1. a single word TaggedPtr entry => true
  //  2. the startAndKind_ word of SlotsOrElementsRange => true
  //     (startAndKind_ is a uintptr_t tagged with SlotsOrElementsKind)
  //  3. the ptr_ word of SlotsOrElementsRange (itself a TaggedPtr) => false
  //
  // To check for case 3, interpret the word as a TaggedPtr: if it is tagged as
  // a SlotsOrElementsRange tagged pointer then we are inside such a range and
  // |index| does not point to the base of an entry. This requires that no
  // startAndKind_ word can be interpreted as such, which is arranged by making
  // SlotsOrElementsRangeTag zero and all SlotsOrElementsKind tags non-zero.

  MOZ_ASSERT(index < capacity_);
  return (stack_[index] & TagMask) != SlotsOrElementsRangeTag;
}

/* static */
void MarkStack::moveAllWork(MarkStack& dst, MarkStack& src) {
  MOZ_ASSERT(src.elementsRangesAreValid == dst.elementsRangesAreValid);

  if (dst.isEmpty()) {
    dst.swap(src);
    return;
  }

  size_t wordsToMove = src.position();

  AutoEnterOOMUnsafeRegion oomUnsafe;
  if (!dst.ensureSpace<false>(wordsToMove)) {
    oomUnsafe.crash("MarkStack::moveAllWork");
  }

  mozilla::PodCopy(dst.end(), src.ptr(0), wordsToMove);
  dst.topIndex_ += wordsToMove;
  src.topIndex_ = 0;  // Doesn't reset capacity.

  MOZ_ASSERT(src.isEmpty());
}

/* static */
size_t MarkStack::moveSomeWork(GCMarker* marker, MarkStack& dst, MarkStack& src,
                               bool allowDistribute) {
  // Move some work from |src| to |dst|. Assumes |dst| is empty.
  //
  // When this method runs during parallel marking, we are on the thread that
  // owns |src|, and the thread that owns |dst| is blocked waiting on the
  // ParallelMarkTask::resumed condition variable.

  MOZ_ASSERT(dst.isEmpty());
  MOZ_ASSERT(src.elementsRangesAreValid == dst.elementsRangesAreValid);

  // Limit the size of moves to stop threads with work spending too much time
  // donating.
  static const size_t MaxWordsToMove = 4096;

  size_t totalWords = src.position();
  size_t wordsToMove = std::min(totalWords / 2, MaxWordsToMove);

  // Mark stack entries do not represent uniform amounts of marking work (they
  // are either single GC things or arbitrarily large arrays) and when the mark
  // stack is small the situation often arises where one thread repeatedly takes
  // what is in effect a small amount of marking work while leaving the other
  // thread with a whole lot more. To split the work up more effectively we
  // randomly distribute stack entries for small stack.
  //
  // This works by randomly choosing one of every pair of entries in |src| and
  // moving it to |dst| (rather than moving half of the stack as a contiguous
  // region).
  //
  // This has the effect of reducing the number of donations between threads. It
  // does not decrease average marking time but it does decrease variance of
  // marking time.
  static constexpr size_t MaxWordsToDistribute = 30;
  if (allowDistribute && totalWords <= MaxWordsToDistribute) {
    if (!dst.ensureSpace(totalWords)) {
      return 0;
    }

    src.topIndex_ = 0;

    // We will use bits from a single 64-bit random number.
    static_assert(HowMany(MaxWordsToDistribute, 2) <= 64);
    uint64_t randomBits = marker->random.ref().next();
    DebugOnly<size_t> randomBitCount = 64;

    size_t i = 0;    // Entry index.
    size_t pos = 0;  // Source stack position.
    uintptr_t* data = src.stack_;
    while (pos < totalWords) {
      MOZ_ASSERT(src.indexIsEntryBase(pos));

      // Randomly chose which stack to copy the entry to, with each half of each
      // pair of entries moving to different stacks.
      MOZ_ASSERT(randomBitCount != 0);
      bool whichStack = (randomBits & 1) ^ (i & 1);
      randomBits >>= i & 1;
      randomBitCount -= i & 1;

      MarkStack& stack = whichStack ? dst : src;

      bool isRange =
          pos < totalWords - 1 && TagIsRangeTag(Tag(data[pos + 1] & TagMask));
      if (isRange) {
        stack.infalliblePush(
            SlotsOrElementsRange::fromBits(data[pos], data[pos + 1]));
        pos += ValueRangeWords;
      } else {
        stack.infalliblePush(TaggedPtr::fromBits(data[pos]));
        pos++;
      }

      i++;
    }

    return totalWords;
  }

  size_t targetPos = src.position() - wordsToMove;

  // Adjust the target position in case it points to the middle of a two word
  // entry.
  if (!src.indexIsEntryBase(targetPos)) {
    targetPos--;
    wordsToMove++;
  }
  MOZ_ASSERT(src.indexIsEntryBase(targetPos));
  MOZ_ASSERT(targetPos < src.position());
  MOZ_ASSERT(targetPos > 0);
  MOZ_ASSERT(wordsToMove == src.position() - targetPos);

  if (!dst.ensureSpace(wordsToMove)) {
    return 0;
  }

  // TODO: This doesn't have good cache behaviour when moving work between
  // threads. It might be better if the original thread ended up with the top
  // part of the stack, in src words if this method stole from the bottom of
  // the stack rather than the top.

  mozilla::PodCopy(dst.end(), src.stack_ + targetPos, wordsToMove);
  dst.topIndex_ += wordsToMove;
  dst.peekPtr().assertValid();

  src.topIndex_ = targetPos;
#ifdef DEBUG
  src.poisonUnused();
#endif
  src.peekPtr().assertValid();
  return wordsToMove;
}

void MarkStack::clearAndResetCapacity() {
  // Fall back to the smaller initial capacity so we don't hold on to excess
  // memory between GCs.
  topIndex_ = 0;
  (void)resetStackCapacity();
}

void MarkStack::clearAndFreeStack() {
  // Free all stack memory so we don't hold on to excess memory between GCs.
  js_free(stack_);
  stack_ = nullptr;
  capacity_ = 0;
  topIndex_ = 0;
}

template <typename T>
inline bool MarkStack::push(T* ptr) {
  return push(TaggedPtr(MapTypeToMarkStackTag<T*>::value, ptr));
}

inline bool MarkStack::pushTempRope(JSRope* rope) {
  return push(TaggedPtr(TempRopeTag, rope));
}

inline bool MarkStack::push(const TaggedPtr& ptr) {
  if (!ensureSpace(1)) {
    return false;
  }

  infalliblePush(ptr);
  return true;
}

inline void MarkStack::infalliblePush(const TaggedPtr& ptr) {
  MOZ_ASSERT(position() + 1 <= capacity());
  *end() = ptr.asBits();
  topIndex_++;
}

inline void MarkStack::infalliblePush(JSObject* obj, SlotsOrElementsKind kind,
                                      size_t start) {
  SlotsOrElementsRange range(kind, obj, start);
  infalliblePush(range);
}

inline void MarkStack::infalliblePush(const SlotsOrElementsRange& range) {
  MOZ_ASSERT(position() + ValueRangeWords <= capacity());

  range.assertValid();
  end()[0] = range.asBits0();
  end()[1] = range.asBits1();
  topIndex_ += ValueRangeWords;
  MOZ_ASSERT(TagIsRangeTag(peekTag()));
}

inline MarkStack::TaggedPtr MarkStack::peekPtr() const {
  MOZ_ASSERT(!isEmpty());
  return TaggedPtr::fromBits(at(topIndex_ - 1));
}

inline MarkStack::Tag MarkStack::peekTag() const {
  MOZ_ASSERT(!isEmpty());
  return peekPtr().tag();
}

inline MarkStack::TaggedPtr MarkStack::popPtr() {
  MOZ_ASSERT(!isEmpty());
  MOZ_ASSERT(!TagIsRangeTag(peekTag()));
  peekPtr().assertValid();
  topIndex_--;
  return TaggedPtr::fromBits(*end());
}

inline MarkStack::SlotsOrElementsRange MarkStack::popSlotsOrElementsRange() {
  MOZ_ASSERT(!isEmpty());
  MOZ_ASSERT(TagIsRangeTag(peekTag()));
  MOZ_ASSERT(position() >= ValueRangeWords);

  topIndex_ -= ValueRangeWords;
  return SlotsOrElementsRange::fromBits(end()[0], end()[1]);
}

template <bool checkMaxCapacity>
inline bool MarkStack::ensureSpace(size_t count) {
  size_t required = topIndex_ + count;
  if (MOZ_LIKELY(required <= capacity())) {
    return true;
  }

  size_t newCapacity = mozilla::RoundUpPow2(required);

#ifdef JS_GC_ZEAL
  if constexpr (checkMaxCapacity) {
    newCapacity = std::min(newCapacity, maxCapacity_.ref());
    if (newCapacity < required) {
      return false;
    }
  }
#endif

  return resize(newCapacity);
}

bool MarkStack::resize(size_t newCapacity) {
  MOZ_ASSERT(newCapacity != 0);
  MOZ_ASSERT(newCapacity >= position());

  auto poisonOnExit = mozilla::MakeScopeExit([this]() { poisonUnused(); });

  if (newCapacity == capacity_) {
    return true;
  }

  uintptr_t* newStack =
      js_pod_realloc<uintptr_t>(stack_, capacity_, newCapacity);
  if (!newStack) {
    return false;
  }

  stack_ = newStack;
  capacity_ = newCapacity;
  return true;
}

inline void MarkStack::poisonUnused() {
  static_assert((JS_FRESH_MARK_STACK_PATTERN & TagMask) > LastTag,
                "The mark stack poison pattern must not look like a valid "
                "tagged pointer");

  MOZ_ASSERT(topIndex_ <= capacity_);
  AlwaysPoison(stack_ + topIndex_, JS_FRESH_MARK_STACK_PATTERN,
               capacity_ - topIndex_, MemCheckKind::MakeUndefined);
}

size_t MarkStack::sizeOfExcludingThis() const {
  return capacity_ * sizeof(uintptr_t);
}

MarkStackIter::MarkStackIter(MarkStack& stack)
    : stack_(stack), pos_(stack.position()) {}

inline size_t MarkStackIter::position() const { return pos_; }

inline bool MarkStackIter::done() const { return position() == 0; }

inline void MarkStackIter::next() {
  if (isSlotsOrElementsRange()) {
    MOZ_ASSERT(position() >= ValueRangeWords);
    pos_ -= ValueRangeWords;
    return;
  }

  MOZ_ASSERT(!done());
  pos_--;
}

inline bool MarkStackIter::isSlotsOrElementsRange() const {
  return TagIsRangeTag(peekTag());
}

inline MarkStack::Tag MarkStackIter::peekTag() const { return peekPtr().tag(); }

inline MarkStack::TaggedPtr MarkStackIter::peekPtr() const {
  MOZ_ASSERT(!done());
  return MarkStack::TaggedPtr::fromBits(stack_.at(pos_ - 1));
}

inline MarkStack::SlotsOrElementsRange MarkStackIter::slotsOrElementsRange()
    const {
  MOZ_ASSERT(TagIsRangeTag(peekTag()));
  MOZ_ASSERT(position() >= ValueRangeWords);

  uintptr_t* ptr = stack_.ptr(pos_ - ValueRangeWords);
  return MarkStack::SlotsOrElementsRange::fromBits(ptr[0], ptr[1]);
}

inline void MarkStackIter::setSlotsOrElementsRange(
    const MarkStack::SlotsOrElementsRange& range) {
  MOZ_ASSERT(isSlotsOrElementsRange());

  uintptr_t* ptr = stack_.ptr(pos_ - ValueRangeWords);
  ptr[0] = range.asBits0();
  ptr[1] = range.asBits1();
}

/*** GCMarker ***************************************************************/

/*
 * WeakMapTraceAction::Expand: the GC is recomputing the liveness of WeakMap
 * entries by expanding each live WeakMap into its constituent key->value edges,
 * a table of which will be consulted in a later phase whenever marking a
 * potential key.
 */

GCMarker::GCMarker(JSRuntime* rt)
    : tracer_(mozilla::VariantType<MarkingTracer>(), rt, this),
      runtime_(rt),
      haveSwappedStacks(false),
      markColor_(MarkColor::Black),
      state(NotActive),
      incrementalWeakMapMarkingEnabled(
          TuningDefaults::IncrementalWeakMapMarkingEnabled),
      random(js::GenerateRandomSeed(), js::GenerateRandomSeed())
#ifdef DEBUG
      ,
      checkAtomMarking(true),
      strictCompartmentChecking(false)
#endif
{
}

bool GCMarker::init() { return stack.init(); }

bool GCMarker::isDrained() const {
#ifdef JS_GC_CONCURRENT_MARKING
  if (!mainThreadBuffersAreEmpty()) {
    return false;
  }
#endif

  return isMarkStackEmpty();
}

void GCMarker::start() {
  MOZ_ASSERT(state == NotActive);
  MOZ_ASSERT(stack.isEmpty());
  state = RegularMarking;
  setMarkColor(MarkColor::Black);
}

static void ClearEphemeronEdges(JSRuntime* rt) {
  for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
    zone->gcEphemeronEdges().clearAndCompact();
  }
}

void GCMarker::deactivate() {
  if (haveSwappedStacks) {
    swapMarkStacks();
  }
  MOZ_ASSERT(markColor() == MarkColor::Black);
  MOZ_ASSERT(!haveSwappedStacks);

  state = NotActive;

  MOZ_ASSERT(isDrained());
  ClearEphemeronEdges(runtime());
  otherStack.clearAndFreeStack();
  unmarkGrayStack.clearAndFree();
}

void GCMarker::stop() {
  MOZ_ASSERT(isDrained());
  MOZ_ASSERT(markColor() == MarkColor::Black);

  if (state == NotActive) {
    MOZ_ASSERT(!haveSwappedStacks);
    return;
  }

  deactivate();
}

void GCRuntime::resetDeferredWeakMaps() {
  for (auto* list : {&blackDeferredMaps, &grayDeferredMaps}) {
    // Move deferred weakmaps back to their per-Zone lists.
    while (auto* map = list->ref().popFirst()) {
      MOZ_ASSERT(!map->isSystem());
      map->zone()->gcMarkedUserWeakMaps().pushBack(map);
    }
  }
}

void GCMarker::reset() {
  state = NotActive;

  stack.clearAndResetCapacity();
  setMarkColor(MarkColor::Black);

#ifdef JS_GC_CONCURRENT_MARKING
  blackMainThreadBuffer_.ref().clearAndFree();
  grayMainThreadBuffer_.ref().clearAndFree();
#endif

  deactivate();
}

void GCMarker::setMarkColor(gc::MarkColor newColor) {
  if (markColor_ == newColor) {
    return;
  }

  markColor_ = newColor;

  // Switch stacks. We only need to do this if there are any stack entries (as
  // empty stacks are interchangeable) or to switch back to the original stack.
  if (!isMarkStackEmpty() ||
      (haveSwappedStacks && newColor == MarkColor::Black)) {
    swapMarkStacks();
  }
}

void GCMarker::swapMarkStacks() {
  stack.swap(otherStack);
  haveSwappedStacks = !haveSwappedStacks;
}

bool GCMarker::hasEntries(MarkColor color) const {
  const MarkStack& stackForColor = color == markColor() ? stack : otherStack;
  return stackForColor.hasEntries();
}

template <typename T>
inline void GCMarker::pushTaggedPtr(T* ptr) {
  MOZ_ASSERT(ptr->isTenured());
  checkZone(ptr);
  if (!stack.push(ptr)) {
    delayMarkingChildrenOnOOM(ptr);
  }
}

inline void GCMarker::pushValueRange(JSObject* obj, SlotsOrElementsKind kind,
                                     size_t start, size_t end) {
  MOZ_ASSERT(obj->isTenured());
  checkZone(obj);
  MOZ_ASSERT(obj->is<NativeObject>());
  MOZ_ASSERT(start <= end);

  if (start != end) {
    stack.infalliblePush(obj, kind, start);
  }
}

void GCMarker::setRootMarkingMode(bool newState) {
  if (newState) {
    setMarkingStateAndTracer<RootMarkingTracer>(RegularMarking, RootMarking);
  } else {
    setMarkingStateAndTracer<MarkingTracer>(RootMarking, RegularMarking);
  }
}

void GCMarker::enterParallelMarkingMode() {
  setMarkingStateAndTracer<ParallelMarkingTracer>(RegularMarking,
                                                  ParallelMarking);
}

void GCMarker::leaveParallelMarkingMode() {
  setMarkingStateAndTracer<MarkingTracer>(ParallelMarking, RegularMarking);
}

void GCMarker::enterConcurrentMarkingMode() {
  setMarkingStateAndTracer<ConcurrentMarkingTracer>(RegularMarking,
                                                    ConcurrentMarking);
}

void GCMarker::leaveConcurrentMarkingMode() {
  setMarkingStateAndTracer<MarkingTracer>(ConcurrentMarking, RegularMarking);
}

void GCMarker::enterSingleThreadedMode() {
  if (state == ParallelMarking) {
    setMarkingStateAndTracer<ParallelMarkingTracer>(
        ParallelMarking, ParallelMarkingSingleThread);
  }
}

void GCMarker::leaveSingleThreadedMode() {
  if (state == ParallelMarkingSingleThread) {
    setMarkingStateAndTracer<ParallelMarkingTracer>(ParallelMarkingSingleThread,
                                                    ParallelMarking);
  }
}

// It may not be worth the overhead of donating very few mark stack entries. For
// some (non-parallelizable) workloads this could lead to constantly
// interrupting marking work and makes parallel marking slower than single
// threaded.
//
// Conversely, we do want to try splitting up work occasionally or we may fail
// to parallelize workloads that result in few mark stack entries.
//
// Therefore we try hard to split work up at the start of a slice (calling
// canDonateWork) but when a slice is running we only donate if there is enough
// work to make it worthwhile (calling shouldDonateWork).
bool GCMarker::canDonateWork() const {
  return stack.position() > ValueRangeWords;
}
bool GCMarker::shouldDonateWork() const {
  constexpr size_t MinWordCount = 12;
  static_assert(MinWordCount >= ValueRangeWords,
                "We must always leave at least one stack entry.");

  return stack.position() > MinWordCount;
}

template <typename Tracer>
void GCMarker::setMarkingStateAndTracer(MarkingState prev, MarkingState next) {
  MOZ_ASSERT(state == prev);
  state = next;
  tracer_.emplace<Tracer>(runtime(), this);
}

bool GCMarker::enterWeakMarkingMode() {
  MOZ_ASSERT(tracer()->weakMapAction() == JS::WeakMapTraceAction::Expand);
  if (!runtime()->gc.haveAllImplicitEdges()) {
    return false;
  }

  // During weak marking mode, we maintain a table mapping weak keys to
  // entries in known-live weakmaps. Initialize it with the keys of marked
  // weakmaps -- or more precisely, the keys of marked weakmaps that are
  // mapped to not yet live values. (Once bug 1167452 implements incremental
  // weakmap marking, this initialization step will become unnecessary, as
  // the table will already hold all such keys.)

  // Set state before doing anything else, so any new key that is marked
  // during the following gcEphemeronEdges scan will itself be looked up in
  // gcEphemeronEdges and marked according to ephemeron rules.
  setMarkingStateAndTracer<WeakMarkingTracer>(RegularMarking, WeakMarking);

  return true;
}

IncrementalProgress JS::Zone::enterWeakMarkingMode(GCMarker* marker,
                                                   SliceBudget& budget) {
  MOZ_ASSERT(isGCMarking());
  MOZ_ASSERT(marker->isWeakMarking());

  if (!marker->incrementalWeakMapMarkingEnabled) {
    ForAllWeakMapsInZone(this, [marker](WeakMapBase* map) {
      if (map->isMarked()) {
        (void)map->markEntries(marker);
      }
    });
    return IncrementalProgress::Finished;
  }

  // gcEphemeronEdges contains the keys from all weakmaps marked so far, or at
  // least the keys that might still need to be marked through. Scan through
  // gcEphemeronEdges and mark all values whose keys are marked. This marking
  // may recursively mark through other weakmap entries (immediately since we
  // are now in WeakMarking mode). The end result is a consistent state where
  // all values are marked if both their map and key are marked -- though note
  // that we may later leave weak marking mode, do some more marking, and then
  // enter back in.

  if (!isGCMarking()) {
    return IncrementalProgress::Finished;
  }

  WeakMarkingTracer* trc = marker->getWeakMarkingTracer();
  for (auto iter = gcEphemeronEdges().iter(); !iter.done(); iter.next()) {
    Cell* src = iter.get().key();
    CellColor srcColor = gc::detail::GetEffectiveColor(marker, src);

    auto& edges = iter.get().value();
    size_t numEdges = edges.length();
    if (IsMarked(srcColor) && edges.length() > 0) {
      trc->markEphemeronEdges(edges, AsMarkColor(srcColor));
    }
    budget.step(1 + numEdges);
    if (budget.isOverBudget()) {
      return NotFinished;
    }
  }

  return IncrementalProgress::Finished;
}

void GCMarker::leaveWeakMarkingMode() {
  if (state == RegularMarking) {
    return;
  }

  setMarkingStateAndTracer<MarkingTracer>(WeakMarking, RegularMarking);

  // The gcEphemeronEdges table is still populated and may be used during a
  // future weak marking mode within this GC.
}

void GCMarker::abortLinearWeakMarking() {
  runtime()->gc.clearHaveAllImplicitEdges();
  if (state == WeakMarking) {
    leaveWeakMarkingMode();
  }
}

MOZ_NEVER_INLINE void GCMarker::delayMarkingChildrenOnOOM(Cell* cell) {
  runtime()->gc.delayMarkingChildren(cell, markColor());
}

bool GCRuntime::hasDelayedMarking() const {
  bool result = delayedMarkingList;
  MOZ_ASSERT(result == (markLaterArenas != 0));
  return result;
}

void GCRuntime::delayMarkingChildren(Cell* cell, MarkColor color) {
  // Synchronize access to delayed marking state during parallel marking.
  LockGuard<Mutex> lock(delayedMarkingLock);

  Arena* arena = cell->asTenured().arena();
  if (!arena->onDelayedMarkingList()) {
    arena->setNextDelayedMarkingArena(delayedMarkingList);
    delayedMarkingList = arena;
#ifdef DEBUG
    markLaterArenas++;
#endif
  }

  if (!arena->hasDelayedMarking(color)) {
    arena->setHasDelayedMarking(color, true);
    delayedMarkingWorkAdded = true;
  }
}

void GCRuntime::markDelayedChildren(Arena* arena, MarkColor color) {
  JSTracer* trc = marker().tracer();
  JS::TraceKind kind = MapAllocToTraceKind(arena->getAllocKind());
  MarkColor colorToCheck =
      TraceKindCanBeMarkedGray(kind) ? color : MarkColor::Black;

  for (ArenaCellIterUnderGC cell(arena); !cell.done(); cell.next()) {
    if (cell->isMarked(colorToCheck)) {
      ApplyGCThingTyped(cell, kind, [trc, this](auto t) {
        // Record the source zone so onEdge can update the atom-marking
        // bitmap for any Symbol edges traced via the generic tracer.
        AutoSetTracingSource asts(trc, t);
        t->traceChildren(trc);
        if (marker().isWeakMarking()) {
          marker().getWeakMarkingTracer()->maybeMarkImplicitEdges(t);
        }
      });
    }
  }
}

/*
 * Process arenas from |delayedMarkingList| by marking the unmarked children of
 * marked cells of color |color|.
 *
 * This is called twice, first to mark gray children and then to mark black
 * children.
 */

void GCRuntime::processDelayedMarkingList(MarkColor color) {
  // Marking delayed children may add more arenas to the list, including arenas
  // we are currently processing or have previously processed. Handle this by
  // clearing a flag on each arena before marking its children. This flag will
  // be set again if the arena is re-added. Iterate the list until no new arenas
  // were added.

  AutoSetMarkColor setColor(marker(), color);
  AutoUpdateMarkStackRanges updateRanges(marker());

  do {
    delayedMarkingWorkAdded = false;
    for (Arena* arena = delayedMarkingList; arena;
         arena = arena->getNextDelayedMarking()) {
      if (arena->hasDelayedMarking(color)) {
        arena->setHasDelayedMarking(color, false);
        markDelayedChildren(arena, color);
      }
    }
    if (marker().hasEntriesForCurrentColor() || hasDeferredWeakMaps(color)) {
      MOZ_ALWAYS_TRUE(marker().matchTracer([](auto& trc) {
        SliceBudget budget = SliceBudget::unlimited();
        return trc.markCurrentColor(budget);
      }));
    }
  } while (delayedMarkingWorkAdded);

  MOZ_ASSERT(marker().isDrained());
  MOZ_ASSERT(blackDeferredMaps.ref().isEmpty());
  MOZ_ASSERT_IF(color == MarkColor::Gray, grayDeferredMaps.ref().isEmpty());
}

void GCRuntime::markAllDelayedChildren(ShouldReportMarkTime reportTime) {
  MOZ_ASSERT(CurrentThreadIsMainThread() || CurrentThreadIsPerformingGC());
  MOZ_ASSERT(marker().isDrained());
  MOZ_ASSERT(!hasAnyDeferredWeakMaps());
  MOZ_ASSERT(hasDelayedMarking());

  mozilla::Maybe<gcstats::AutoPhase> ap;
  if (reportTime) {
    ap.emplace(stats(), gcstats::PhaseKind::MARK_DELAYED);
  }

  // We have a list of arenas containing marked cells with unmarked children
  // where we ran out of stack space during marking. Both black and gray cells
  // in these arenas may have unmarked children. Mark black children first.

  const MarkColor colors[] = {MarkColor::Black, MarkColor::Gray};
  for (MarkColor color : colors) {
    processDelayedMarkingList(color);
    rebuildDelayedMarkingList();
  }

  MOZ_ASSERT(!hasDelayedMarking());
  MOZ_ASSERT(!hasAnyDeferredWeakMaps());
}

void GCRuntime::rebuildDelayedMarkingList() {
  // Rebuild the delayed marking list, removing arenas which do not need further
  // marking.

  Arena* listTail = nullptr;
  forEachDelayedMarkingArena([&](Arena* arena) {
    if (!arena->hasAnyDelayedMarking()) {
      arena->clearDelayedMarkingState();
#ifdef DEBUG
      MOZ_ASSERT(markLaterArenas);
      markLaterArenas--;
#endif
      return;
    }

    appendToDelayedMarkingList(&listTail, arena);
  });
  appendToDelayedMarkingList(&listTail, nullptr);
}

void GCRuntime::resetDelayedMarking() {
  MOZ_ASSERT(CurrentThreadIsMainThread());

  forEachDelayedMarkingArena([&](Arena* arena) {
    MOZ_ASSERT(arena->onDelayedMarkingList());
    arena->clearDelayedMarkingState();
#ifdef DEBUG
    MOZ_ASSERT(markLaterArenas);
    markLaterArenas--;
#endif
  });
  delayedMarkingList = nullptr;
  MOZ_ASSERT(!markLaterArenas);
}

inline void GCRuntime::appendToDelayedMarkingList(Arena** listTail,
                                                  Arena* arena) {
  if (*listTail) {
    (*listTail)->updateNextDelayedMarkingArena(arena);
  } else {
    delayedMarkingList = arena;
  }
  *listTail = arena;
}

template <typename F>
inline void GCRuntime::forEachDelayedMarkingArena(F&& f) {
  Arena* arena = delayedMarkingList;
  Arena* next;
  while (arena) {
    next = arena->getNextDelayedMarking();
    f(arena);
    arena = next;
  }
}

#ifdef DEBUG
void GCMarker::checkZone(Cell* cell) {
  MOZ_ASSERT(state != NotActive);
  if (cell->isTenured()) {
    Zone* zone = cell->asTenured().zone();
    MOZ_ASSERT(zone->isGCMarkingOrVerifyingPreBarriers() ||
               zone->isAtomsZone());
  }
}
#endif

size_t GCMarker::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
  return mallocSizeOf(this) + stack.sizeOfExcludingThis() +
         otherStack.sizeOfExcludingThis();
}

/*** IsMarked / IsAboutToBeFinalized ****************************************/

template <typename T>
static inline void CheckIsMarkedThing(T* thing) {
#define IS_SAME_TYPE_OR(name, type, _, _1) std::is_same_v<type, T> ||
  static_assert(JS_FOR_EACH_TRACEKIND(IS_SAME_TYPE_OR) false,
                "Only the base cell layout types are allowed into "
                "marking/tracing internals");
#undef IS_SAME_TYPE_OR

#ifdef DEBUG
  MOZ_ASSERT(thing);

  // Allow any thread access to uncollected things.
  Zone* zone = thing->zoneFromAnyThread();
  if (thing->isPermanentAndMayBeShared()) {
    // Shared things are not collected and should always be marked, except
    // during shutdown when we've merged shared atoms back into the main atoms
    // zone.
    if (zone->wasGCStarted()) {
      MOZ_ASSERT(!zone->runtimeFromAnyThread()->gc.maybeSharedAtomsZone());
      return;
    }
    MOZ_ASSERT(!zone->needsMarkingBarrier());
    MOZ_ASSERT(thing->isMarkedBlack());
    return;
  }

  // Allow the current thread access if it is sweeping or in sweep-marking, but
  // try to check the zone. Some threads have access to all zones when sweeping.
  JS::GCContext* gcx = TlsGCContext.get();
  MOZ_ASSERT(gcx->gcUse() != GCUse::Finalizing);
  if (gcx->gcUse() == GCUse::Sweeping || gcx->gcUse() == GCUse::Marking) {
    MOZ_ASSERT_IF(gcx->gcSweepZone(),
                  gcx->gcSweepZone() == zone || zone->isAtomsZone());
    return;
  }

  // Otherwise only allow access from the main thread or this zone's associated
  // thread.
  MOZ_ASSERT(CurrentThreadCanAccessRuntime(thing->runtimeFromAnyThread()) ||
             CurrentThreadCanAccessZone(thing->zoneFromAnyThread()));
#endif
}

template <typename T>
bool js::gc::IsMarkedInternal(JSRuntime* rt, T* thing) {
  // Don't depend on the mark state of other cells during finalization.
  MOZ_ASSERT(!CurrentThreadIsGCFinalizing());
  MOZ_ASSERT(rt->heapState() != JS::HeapState::MinorCollecting);
  MOZ_ASSERT(thing);
  CheckIsMarkedThing(thing);

  // This is not used during minor sweeping nor used to update moved GC things.
  MOZ_ASSERT(!IsForwarded(thing));

  // Permanent things are never marked by non-owning runtimes.
  TenuredCell* cell = &thing->asTenured();
  Zone* zone = cell->zoneFromAnyThread();
#ifdef DEBUG
  if (IsOwnedByOtherRuntime(rt, thing)) {
    MOZ_ASSERT(!zone->wasGCStarted());
    MOZ_ASSERT(thing->isMarkedBlack());
  }
#endif

  return !zone->isGCMarking() || TenuredThingIsMarkedAny(thing);
}

template <typename T>
bool js::gc::IsAboutToBeFinalizedInternal(T* thing) {
  // Don't depend on the mark state of other cells during finalization.
  MOZ_ASSERT(!CurrentThreadIsGCFinalizing());
  MOZ_ASSERT(thing);
  CheckIsMarkedThing(thing);

  // This is not used during minor sweeping nor used to update moved GC things.
  MOZ_ASSERT(!IsForwarded(thing));

  if (!thing->isTenured()) {
    return false;
  }

  // Permanent things are never finalized by non-owning runtimes.
  TenuredCell* cell = &thing->asTenured();
  Zone* zone = cell->zoneFromAnyThread();
#ifdef DEBUG
  JSRuntime* rt = TlsGCContext.get()->runtimeFromAnyThread();
  if (IsOwnedByOtherRuntime(rt, thing)) {
    MOZ_ASSERT(!zone->wasGCStarted());
    MOZ_ASSERT(thing->isMarkedBlack());
  }
#endif

  return zone->isGCSweeping() && !TenuredThingIsMarkedAny(thing);
}

template <typename T>
bool js::gc::IsAboutToBeFinalizedInternal(const T& thing) {
  bool dying = false;
  ApplyGCThingTyped(
      thing, [&dying](auto t) { dying = IsAboutToBeFinalizedInternal(t); });
  return dying;
}

SweepingTracer::SweepingTracer(JSRuntime* rt)
    : GenericTracerImpl(rt, JS::TracerKind::Sweeping,
                        JS::WeakMapTraceAction::TraceKeysAndValues) {}

template <typename T>
inline bool SweepingTracer::onEdge(T** thingp, const char* name) {
  T* thing = *thingp;
  if (!thing) {
    return true;
  }

  CheckIsMarkedThing(thing);

  if (!thing->isTenured()) {
    return true;
  }

  TenuredCell* cell = &thing->asTenured();
  Zone* zone = cell->zoneFromAnyThread();

#ifdef DEBUG
  // Permanent things are never finalized by non-owning runtimes.
  if (IsOwnedByOtherRuntime(runtime(), thing)) {
    MOZ_ASSERT(!zone->wasGCStarted());
    MOZ_ASSERT(thing->isMarkedBlack());
  }

  // Any zone can contain references to symbols so make sure we've finished
  // marking them before we try and sweep them. If this fails then we missed
  // adding a sweep group edge somewhere. This check can be disabled in places
  // where we only care about references from the current zone.
  if (cell->getTraceKind() == JS::TraceKind::Symbol && !cell->isMarkedBlack() &&
      !allowSweepingSymbolsEarly) {
    MOZ_ASSERT(!zone->isGCMarking());
  }
#endif

  // It would be nice if we could assert that the zone of the tenured cell is in
  // the Sweeping state, but that isn't always true for:
  //  - atoms
  //  - the jitcode map
  //  - the mark queue
  bool sweepZone =
      zone->isGCSweeping() || (zone->isAtomsZone() && zone->isGCMarking());
  return !(sweepZone && !cell->isMarkedAny());
}

namespace js::gc {

template <typename T>
JS_PUBLIC_API bool TraceWeakEdge(JSTracer* trc, JS::Heap<T>* thingp) {
  return TraceEdgeInternal(trc, gc::ConvertToBase(thingp->unsafeAddress()),
                           "JS::Heap edge");
}

template <typename T>
JS_PUBLIC_API bool EdgeNeedsSweepUnbarrieredSlow(T* thingp) {
  return IsAboutToBeFinalizedInternal(*ConvertToBase(thingp));
}

// Instantiate a copy of the Tracing templates for each public GC type.
#define INSTANTIATE_ALL_VALID_HEAP_TRACE_FUNCTIONS(type)            \
  template JS_PUBLIC_API bool TraceWeakEdge<type>(JSTracer * trc,   \
                                                  JS::Heap<type>*); \
  template JS_PUBLIC_API bool EdgeNeedsSweepUnbarrieredSlow<type>(type*);
JS_FOR_EACH_PUBLIC_GC_POINTER_TYPE(INSTANTIATE_ALL_VALID_HEAP_TRACE_FUNCTIONS)
JS_FOR_EACH_PUBLIC_TAGGED_GC_POINTER_TYPE(
    INSTANTIATE_ALL_VALID_HEAP_TRACE_FUNCTIONS)

#define INSTANTIATE_INTERNAL_IS_MARKED_FUNCTION(type) \
  template bool IsMarkedInternal(JSRuntime* rt, type thing);

#define INSTANTIATE_INTERNAL_IATBF_FUNCTION(type) \
  template bool IsAboutToBeFinalizedInternal(type thingp);

#define INSTANTIATE_INTERNAL_MARKING_FUNCTIONS_FROM_TRACEKIND(_1, type, _2, \
                                                              _3)           \
  INSTANTIATE_INTERNAL_IS_MARKED_FUNCTION(type*)                            \
  INSTANTIATE_INTERNAL_IATBF_FUNCTION(type*)

JS_FOR_EACH_TRACEKIND(INSTANTIATE_INTERNAL_MARKING_FUNCTIONS_FROM_TRACEKIND)

#define INSTANTIATE_IATBF_FUNCTION_FOR_TAGGED_POINTER(type) \
  INSTANTIATE_INTERNAL_IATBF_FUNCTION(const type&)

JS_FOR_EACH_PUBLIC_TAGGED_GC_POINTER_TYPE(
    INSTANTIATE_IATBF_FUNCTION_FOR_TAGGED_POINTER)

#undef INSTANTIATE_INTERNAL_IS_MARKED_FUNCTION
#undef INSTANTIATE_INTERNAL_IATBF_FUNCTION
#undef INSTANTIATE_INTERNAL_MARKING_FUNCTIONS_FROM_TRACEKIND
#undef INSTANTIATE_IATBF_FUNCTION_FOR_TAGGED_POINTER

}  // namespace js::gc

/*** Cycle Collector Barrier Implementation *********************************/

/*
 * The GC and CC are run independently. Consequently, the following sequence of
 * events can occur:
 * 1. GC runs and marks an object gray.
 * 2. The mutator runs (specifically, some C++ code with access to gray
 *    objects) and creates a pointer from a JS root or other black object to
 *    the gray object. If we re-ran a GC at this point, the object would now be
 *    black.
 * 3. Now we run the CC. It may think it can collect the gray object, even
 *    though it's reachable from the JS heap.
 *
 * To prevent this badness, we unmark the gray bit of an object when it is
 * accessed by callers outside XPConnect. This would cause the object to go
 * black in step 2 above. This must be done on everything reachable from the
 * object being returned. The following code takes care of the recursive
 * re-coloring.
 *
 * There is an additional complication for certain kinds of edges that are not
 * contained explicitly in the source object itself, such as from a weakmap key
 * to its value. These "implicit edges" are represented in some other
 * container object, such as the weakmap itself. In these
 * cases, calling unmark gray on an object won't find all of its children.
 *
 * Handling these implicit edges has two parts:
 * - A special pass enumerating all of the containers that know about the
 *   implicit edges to fix any black-gray edges that have been created. This
 *   is implemented in nsXPConnect::FixWeakMappingGrayBits.
 * - To prevent any incorrectly gray objects from escaping to live JS outside
 *   of the containers, we must add unmark-graying read barriers to these
 *   containers.
 */


#ifdef DEBUG
struct AssertNonGrayTracer final : public JS::CallbackTracer {
  // This is used by the UnmarkGray tracer only, and needs to report itself as
  // the non-gray tracer to not trigger assertions.  Do not use it in another
  // context without making this more generic.
  explicit AssertNonGrayTracer(JSRuntime* rt)
      : JS::CallbackTracer(rt, JS::TracerKind::UnmarkGray) {}
  bool onChild(JS::GCCellPtr thing, const char* name) override {
    MOZ_ASSERT(!thing.asCell()->isMarkedGray());
    return true;
  }
};
#endif

template <uint32_t markingOptions>
class js::gc::UnmarkGrayTracer final
    : public GenericTracerImpl<UnmarkGrayTracer<markingOptions>> {
  using Base = GenericTracerImpl<UnmarkGrayTracer<markingOptions>>;
  using BarrierTracer = MarkingTracerT<markingOptions>;

 public:
  // We set weakMapAction to WeakMapTraceAction::Skip because the cycle
  // collector will fix up any color mismatches involving weakmaps when it runs.
  explicit UnmarkGrayTracer(BarrierTracer* barrierTracer)
      : Base(barrierTracer->runtime(), JS::TracerKind::UnmarkGray,
             JS::TraceOptions(JS::WeakMapTraceAction::Skip,
                              JS::WeakEdgeTraceAction::Skip)),
        unmarkedAny(false),
        oom(false),
        barrierTracer(barrierTracer),
        stack(barrierTracer->gcMarker()->unmarkGrayStack) {}

  void unmark(JS::GCCellPtr cell);

  // Whether we unmarked anything.
  bool unmarkedAny;

  // Whether we ran out of memory.
  bool oom;

 private:
  // Tracer to use if we need to unmark in zones that are currently being
  // marked.
  BarrierTracer* barrierTracer;

  // The source of edges traversed by onChild.
  Zone* sourceZone;

  // Stack of cells to traverse.
  Vector<JS::GCCellPtr, 0, SystemAllocPolicy>& stack;

  template <typename T>
  bool onChild(T* thing);

  template <typename T>
  bool onEdge(T** thingp, const char* name) {
    if (T* thing = *thingp) {
      return onChild(thing);
    }
    return true;
  }
  friend class js::GenericTracerImpl<UnmarkGrayTracer<markingOptions>>;
};

template <uint32_t opts>
template <typename T>
bool UnmarkGrayTracer<opts>::onChild(T* thing) {
  // Cells in the nursery cannot be gray, and nor can certain kinds of tenured
  // cells. These must necessarily point only to black edges.
  if (!TraceKindCanBeGray<T>::value || !thing->isTenured()) {
#ifdef DEBUG
    MOZ_ASSERT(!thing->isMarkedGray());
    AssertNonGrayTracer nongray(this->runtime());
    thing->traceChildren(&nongray);
#endif
    return true;
  }

  TenuredCell& tenured = thing->asTenured();
  Zone* zone = tenured.zoneFromAnyThread();

  // As well as updating the mark bits, we may need to update the color in the
  // atom marking bitmap for symbols to record that |sourceZone| now has a black
  // edge to |thing|.
  if constexpr (std::is_same_v<T, JS::Symbol>) {
    MOZ_ASSERT(zone->isAtomsZone());
    if (sourceZone) {
      GCRuntime* gc = &this->runtime()->gc;
      gc->atomMarking.maybeUnmarkGrayAtomically(sourceZone, thing);
    }
  }

  // If the cell is in a zone whose mark bits are being cleared, then it will
  // end up being marked black by GC marking.
  if (zone->isGCPreparing()) {
    return true;
  }

  // If the cell is already marked black then there's nothing more to do.
  if (tenured.isMarkedBlack()) {
    return true;
  }

  if (zone->isGCMarking()) {
    // If the cell is in a zone that we're currently marking then it's possible
    // that it is currently white (but would have ended up gray). To handle this
    // case, mark the cell with the current barrier tracer. This will ensure it
    // eventually gets marked black.

    GCMarker* marker = barrierTracer->gcMarker();
#ifdef DEBUG
    MOZ_ASSERT(marker->markColor() == MarkColor::Black);
    AutoSetThreadIsMarking threadIsMarking;
#endif  // DEBUG

    AutoClearTracingSource acts(marker);

    MOZ_ASSERT(ShouldMark(MarkColor::Black, thing));
    CheckTracedThing(barrierTracer, thing);
    barrierTracer->markAndTraverse(thing);
  } else if (tenured.isMarkedGray()) {
    if constexpr (bool(opts & MarkingOptions::AtomicMarking)) {
      tenured.markBlackAtomic();
    } else {
      tenured.markBlack();
    }
    if (!stack.append(thing)) {
      oom = true;
    }
  }

  unmarkedAny = true;
  return true;
}

template <uint32_t opts>
void UnmarkGrayTracer<opts>::unmark(JS::GCCellPtr cell) {
  MOZ_ASSERT(stack.empty());

  // TODO: We probably don't need to do anything if the gray bits are
  // invalid. However an early return here causes ExposeGCThingToActiveJS to
  // fail because it asserts that something gets unmarked.

  sourceZone = nullptr;
  ApplyGCThingTyped(cell, [&](auto* thing) { onChild(thing); });

  while (!stack.empty() && !oom) {
    JS::GCCellPtr thing = stack.popCopy();
    sourceZone = thing.asCell()->zone();
    TraceChildren(this, thing);
  }

  if (oom) {
    // If we run out of memory, we take a drastic measure: require that we
    // GC again before the next CC.
    stack.clear();
    this->runtime()->gc.setGrayBitsInvalid();
  }
}

bool js::gc::UnmarkGrayGCThingUnchecked(GCMarker* marker, JS::GCCellPtr thing) {
  MOZ_ASSERT(thing);
  return marker->matchTracer([thing](auto& trc) {
    UnmarkGrayTracer unmarker(&trc);
    unmarker.unmark(thing);
    return unmarker.unmarkedAny;
  });
}

JS_PUBLIC_API bool JS::UnmarkGrayGCThingRecursively(JS::GCCellPtr thing) {
  MOZ_ASSERT(!JS::RuntimeHeapIsCollecting());
  MOZ_ASSERT(!JS::RuntimeHeapIsCycleCollecting());

  mozilla::Maybe<AutoGeckoProfilerEntry> profilingStackFrame;
  if (JSContext* cx = TlsContext.get()) {
    profilingStackFrame.emplace(cx, "UnmarkGrayGCThing",
                                JS::ProfilingCategoryPair::GCCC_UnmarkGray);
  }

  JSRuntime* rt = thing.asCell()->runtimeFromMainThread();
  if (thing.asCell()->zone()->isGCPreparing()) {
    // Mark bits are being cleared in preparation for GC.
    return false;
  }

  MOZ_ASSERT(thing.asCell()->isMarkedGray());
  return UnmarkGrayGCThingUnchecked(&rt->gc.marker(), thing);
}

void js::gc::UnmarkGrayGCThingRecursively(TenuredCell* cell) {
  JS::UnmarkGrayGCThingRecursively(JS::GCCellPtr(cell, cell->getTraceKind()));
}

#ifdef DEBUG
Cell* js::gc::UninlinedForwarded(const Cell* cell) { return Forwarded(cell); }
#endif

namespace js::debug {

MarkInfo GetMarkInfo(void* vp) {
  GCRuntime& gc = TlsGCContext.get()->runtime()->gc;
  if (gc.nursery().isInside(vp)) {
    ChunkBase* chunk = js::gc::detail::GetGCAddressChunkBase(vp);
    return chunk->getKind() == js::gc::ChunkKind::NurseryFromSpace
               ? MarkInfo::NURSERY_FROMSPACE
               : MarkInfo::NURSERY_TOSPACE;
  }

  if (gc.isPointerWithinBufferAlloc(vp)) {
    return MarkInfo::BUFFER;
  }

  if (!gc.isPointerWithinTenuredCell(vp)) {
    return MarkInfo::UNKNOWN;
  }

  if (!IsCellPointerValid(vp)) {
    return MarkInfo::UNKNOWN;
  }

  TenuredCell* cell = reinterpret_cast<TenuredCell*>(vp);
  if (cell->isMarkedGray()) {
    return MarkInfo::GRAY;
  }
  if (cell->isMarkedBlack()) {
    return MarkInfo::BLACK;
  }
  return MarkInfo::UNMARKED;
}

uintptr_t* GetMarkWordAddress(Cell* cell) {
  if (!cell->isTenured()) {
    return nullptr;
  }

  AtomicBitmapWord* wordp;
  uintptr_t mask;
  ArenaChunkBase* chunk = gc::detail::GetCellChunkBase(&cell->asTenured());
  chunk->markBits.getMarkWordAndMask(&cell->asTenured(), ColorBit::BlackBit,
                                     &wordp, &mask);
  return reinterpret_cast<uintptr_t*>(wordp);
}

uintptr_t GetMarkMask(Cell* cell, uint32_t colorBit) {
  MOZ_ASSERT(colorBit == 0 || colorBit == 1);

  if (!cell->isTenured()) {
    return 0;
  }

  ColorBit bit = colorBit == 0 ? ColorBit::BlackBit : ColorBit::GrayOrBlackBit;
  AtomicBitmapWord* wordp;
  uintptr_t mask;
  ArenaChunkBase* chunk = gc::detail::GetCellChunkBase(&cell->asTenured());
  chunk->markBits.getMarkWordAndMask(&cell->asTenured(), bit, &wordp, &mask);
  return mask;
}

}  // namespace js::debug

Messung V0.5 in Prozent
C=90 H=96 G=93

¤ Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.0.56Bemerkung:  ¤

*Bot Zugriff






Normalansicht

Ziele

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Ergonomie der
Schnittstellen

Diese beiden folgenden Angebotsgruppen bietet das Unternehmen

Angebot

Hier finden Sie eine Liste der Produkte des Unternehmens






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=141584
#Domains=752002