tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

Tenuring.cpp (57084B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sts=2 et sw=2 tw=80:
      3 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 /*
      8 * Implementation of nursery eviction (tenuring).
      9 */
     10 
     11 #include "gc/Tenuring.h"
     12 
     13 #include "gc/Cell.h"
     14 #include "gc/GCInternals.h"
     15 #include "gc/GCProbes.h"
     16 #include "gc/Pretenuring.h"
     17 #include "gc/Zone.h"
     18 #include "jit/JitCode.h"
     19 #include "js/TypeDecls.h"
     20 #include "proxy/Proxy.h"
     21 #include "vm/BigIntType.h"
     22 #include "vm/JSScript.h"
     23 #include "vm/NativeObject.h"
     24 #include "vm/Runtime.h"
     25 #include "vm/TypedArrayObject.h"
     26 
     27 #include "gc/Heap-inl.h"
     28 #include "gc/Marking-inl.h"
     29 #include "gc/ObjectKind-inl.h"
     30 #include "gc/StoreBuffer-inl.h"
     31 #include "gc/TraceMethods-inl.h"
     32 #include "vm/JSObject-inl.h"
     33 #include "vm/PlainObject-inl.h"
     34 #include "vm/StringType-inl.h"
     35 using namespace js;
     36 using namespace js::gc;
     37 
     38 constexpr size_t MAX_DEDUPLICATABLE_STRING_LENGTH = 500;
     39 
     40 #ifdef JS_GC_ZEAL
     41 class js::gc::PromotionStats {
     42  static constexpr size_t AttentionThreshold = 100;
     43 
     44  size_t objectCount = 0;
     45  size_t stringCount = 0;
     46  size_t bigIntCount = 0;
     47  size_t getterSetterCount = 0;
     48 
     49  using BaseShapeCountMap =
     50      HashMap<BaseShape*, size_t, PointerHasher<BaseShape*>, SystemAllocPolicy>;
     51  BaseShapeCountMap objectCountByBaseShape;
     52 
     53  using AllocKindCountArray =
     54      mozilla::EnumeratedArray<AllocKind, size_t, size_t(AllocKind::LIMIT)>;
     55  AllocKindCountArray stringCountByKind;
     56 
     57  bool hadOOM = false;
     58 
     59  struct LabelAndCount {
     60    char label[32] = {'\0'};
     61    size_t count = 0;
     62  };
     63  using CountsVector = Vector<LabelAndCount, 0, SystemAllocPolicy>;
     64 
     65 public:
     66  void notePromotedObject(JSObject* obj);
     67  void notePromotedString(JSString* str);
     68  void notePromotedBigInt(JS::BigInt* bi);
     69  void notePromotedGetterSetter(GetterSetter* gs);
     70 
     71  bool shouldPrintReport() const;
     72  void printReport(JSContext* cx, const JS::AutoRequireNoGC& nogc);
     73 
     74 private:
     75  void printObjectCounts(JSContext* cx, const JS::AutoRequireNoGC& nogc);
     76  void printStringCounts();
     77 
     78  void printCounts(CountsVector& counts, size_t total);
     79  void printLine(const char* name, size_t count, size_t total);
     80 
     81  UniqueChars getConstructorName(JSContext* cx, BaseShape* baseShape,
     82                                 const JS::AutoRequireNoGC& nogc);
     83 };
     84 #endif  // JS_GC_ZEAL
     85 
     86 TenuringTracer::TenuringTracer(JSRuntime* rt, Nursery* nursery,
     87                               bool tenureEverything)
     88    : JSTracer(rt, JS::TracerKind::Tenuring,
     89               JS::WeakMapTraceAction::TraceKeysAndValues),
     90      nursery_(*nursery),
     91      tenureEverything(tenureEverything) {
     92  stringDeDupSet.emplace();
     93 }
     94 
     95 TenuringTracer::~TenuringTracer() = default;
     96 
     97 size_t TenuringTracer::getPromotedSize() const {
     98  return promotedSize + promotedCells * sizeof(NurseryCellHeader);
     99 }
    100 
    101 size_t TenuringTracer::getPromotedCells() const { return promotedCells; }
    102 
    103 void TenuringTracer::onObjectEdge(JSObject** objp, const char* name) {
    104  JSObject* obj = *objp;
    105  if (!nursery_.inCollectedRegion(obj)) {
    106    MOZ_ASSERT(!obj->isForwarded());
    107    return;
    108  }
    109 
    110  *objp = promoteOrForward(obj);
    111  MOZ_ASSERT(!(*objp)->isForwarded());
    112 }
    113 
    114 JSObject* TenuringTracer::promoteOrForward(JSObject* obj) {
    115  MOZ_ASSERT(nursery_.inCollectedRegion(obj));
    116 
    117  if (obj->isForwarded()) {
    118    const gc::RelocationOverlay* overlay = gc::RelocationOverlay::fromCell(obj);
    119    obj = static_cast<JSObject*>(overlay->forwardingAddress());
    120    if (IsInsideNursery(obj)) {
    121      promotedToNursery = true;
    122    }
    123    return obj;
    124  }
    125 
    126  return promoteObject(obj);
    127 }
    128 
    129 JSObject* TenuringTracer::promoteObject(JSObject* obj) {
    130  MOZ_ASSERT(IsInsideNursery(obj));
    131  MOZ_ASSERT(!obj->isForwarded());
    132 
    133 #ifdef JS_GC_ZEAL
    134  if (promotionStats) {
    135    promotionStats->notePromotedObject(obj);
    136  }
    137 #endif
    138 
    139  // Take a fast path for promoting a plain object as this is by far the most
    140  // common case.
    141  if (obj->is<PlainObject>()) {
    142    return promotePlainObject(&obj->as<PlainObject>());
    143  }
    144 
    145  return promoteObjectSlow(obj);
    146 }
    147 
    148 void TenuringTracer::onStringEdge(JSString** strp, const char* name) {
    149  JSString* str = *strp;
    150  if (!nursery_.inCollectedRegion(str)) {
    151    return;
    152  }
    153 
    154  *strp = promoteOrForward(str);
    155 }
    156 
    157 JSString* TenuringTracer::promoteOrForward(JSString* str) {
    158  MOZ_ASSERT(nursery_.inCollectedRegion(str));
    159 
    160  if (str->isForwarded()) {
    161    const gc::RelocationOverlay* overlay = gc::RelocationOverlay::fromCell(str);
    162    str = static_cast<JSString*>(overlay->forwardingAddress());
    163    if (IsInsideNursery(str)) {
    164      promotedToNursery = true;
    165    }
    166    return str;
    167  }
    168 
    169  return promoteString(str);
    170 }
    171 
    172 void TenuringTracer::onBigIntEdge(JS::BigInt** bip, const char* name) {
    173  JS::BigInt* bi = *bip;
    174  if (!nursery_.inCollectedRegion(bi)) {
    175    return;
    176  }
    177 
    178  *bip = promoteOrForward(bi);
    179 }
    180 
    181 JS::BigInt* TenuringTracer::promoteOrForward(JS::BigInt* bi) {
    182  MOZ_ASSERT(nursery_.inCollectedRegion(bi));
    183 
    184  if (bi->isForwarded()) {
    185    const gc::RelocationOverlay* overlay = gc::RelocationOverlay::fromCell(bi);
    186    bi = static_cast<JS::BigInt*>(overlay->forwardingAddress());
    187    if (IsInsideNursery(bi)) {
    188      promotedToNursery = true;
    189    }
    190    return bi;
    191  }
    192 
    193  return promoteBigInt(bi);
    194 }
    195 
    196 void TenuringTracer::onGetterSetterEdge(GetterSetter** gsp, const char* name) {
    197  GetterSetter* gs = *gsp;
    198  if (!nursery_.inCollectedRegion(gs)) {
    199    return;
    200  }
    201 
    202  *gsp = promoteOrForward(gs);
    203 }
    204 
    205 GetterSetter* TenuringTracer::promoteOrForward(GetterSetter* gs) {
    206  MOZ_ASSERT(nursery_.inCollectedRegion(gs));
    207 
    208  if (gs->isForwarded()) {
    209    const gc::RelocationOverlay* overlay = gc::RelocationOverlay::fromCell(gs);
    210    gs = static_cast<GetterSetter*>(overlay->forwardingAddress());
    211    if (IsInsideNursery(gs)) {
    212      promotedToNursery = true;
    213    }
    214    return gs;
    215  }
    216 
    217  return promoteGetterSetter(gs);
    218 }
    219 
    220 // Ignore edges to cell kinds that are not allocated in the nursery.
    221 void TenuringTracer::onSymbolEdge(JS::Symbol** symp, const char* name) {}
    222 void TenuringTracer::onScriptEdge(BaseScript** scriptp, const char* name) {}
    223 void TenuringTracer::onShapeEdge(Shape** shapep, const char* name) {}
    224 void TenuringTracer::onRegExpSharedEdge(RegExpShared** sharedp,
    225                                        const char* name) {}
    226 void TenuringTracer::onBaseShapeEdge(BaseShape** basep, const char* name) {}
    227 void TenuringTracer::onPropMapEdge(PropMap** mapp, const char* name) {}
    228 void TenuringTracer::onJitCodeEdge(jit::JitCode** codep, const char* name) {}
    229 void TenuringTracer::onScopeEdge(Scope** scopep, const char* name) {}
    230 
    231 void TenuringTracer::traverse(JS::Value* thingp) {
    232  MOZ_ASSERT(!nursery().inCollectedRegion(thingp));
    233 
    234  Value value = *thingp;
    235  CheckTracedThing(this, value);
    236 
    237  if (!value.isGCThing()) {
    238    return;
    239  }
    240 
    241  Cell* cell = value.toGCThing();
    242  if (!nursery_.inCollectedRegion(cell)) {
    243    return;
    244  }
    245 
    246  if (cell->isForwarded()) {
    247    const gc::RelocationOverlay* overlay =
    248        gc::RelocationOverlay::fromCell(cell);
    249    Cell* target = overlay->forwardingAddress();
    250    thingp->changeGCThingPayload(target);
    251    if (IsInsideNursery(target)) {
    252      promotedToNursery = true;
    253    }
    254    return;
    255  }
    256 
    257  // We only care about a few kinds of GC thing here and this generates much
    258  // tighter code than using MapGCThingTyped.
    259  if (value.isObject()) {
    260    JSObject* obj = promoteObject(&value.toObject());
    261    MOZ_ASSERT(obj != &value.toObject());
    262    *thingp = JS::ObjectValue(*obj);
    263    return;
    264  }
    265  if (value.isString()) {
    266    JSString* str = promoteString(value.toString());
    267    MOZ_ASSERT(str != value.toString());
    268    *thingp = JS::StringValue(str);
    269    return;
    270  }
    271  if (value.isPrivateGCThing()) {
    272    GetterSetter* gs =
    273        promoteGetterSetter(value.toGCThing()->as<GetterSetter>());
    274    MOZ_ASSERT(gs != value.toGCThing());
    275    *thingp = JS::PrivateGCThingValue(gs);
    276    return;
    277  }
    278  MOZ_ASSERT(value.isBigInt());
    279  JS::BigInt* bi = promoteBigInt(value.toBigInt());
    280  MOZ_ASSERT(bi != value.toBigInt());
    281  *thingp = JS::BigIntValue(bi);
    282 }
    283 
    284 void TenuringTracer::traverse(wasm::AnyRef* thingp) {
    285  MOZ_ASSERT(!nursery().inCollectedRegion(thingp));
    286 
    287  wasm::AnyRef value = *thingp;
    288  CheckTracedThing(this, value);
    289 
    290  Cell* cell = value.toGCThing();
    291  if (!nursery_.inCollectedRegion(cell)) {
    292    return;
    293  }
    294 
    295  wasm::AnyRef post = wasm::AnyRef::invalid();
    296  switch (value.kind()) {
    297    case wasm::AnyRefKind::Object: {
    298      JSObject* obj = promoteOrForward(&value.toJSObject());
    299      MOZ_ASSERT(obj != &value.toJSObject());
    300      post = wasm::AnyRef::fromJSObject(*obj);
    301      break;
    302    }
    303    case wasm::AnyRefKind::String: {
    304      JSString* str = promoteOrForward(value.toJSString());
    305      MOZ_ASSERT(str != value.toJSString());
    306      post = wasm::AnyRef::fromJSString(str);
    307      break;
    308    }
    309    case wasm::AnyRefKind::I31:
    310    case wasm::AnyRefKind::Null: {
    311      // This function must only be called for GC things.
    312      MOZ_CRASH();
    313    }
    314  }
    315 
    316  *thingp = post;
    317 }
    318 
    319 class MOZ_RAII TenuringTracer::AutoPromotedAnyToNursery {
    320 public:
    321  explicit AutoPromotedAnyToNursery(TenuringTracer& trc) : trc_(trc) {
    322    trc.promotedToNursery = false;
    323  }
    324  explicit operator bool() const { return trc_.promotedToNursery; }
    325 
    326 private:
    327  TenuringTracer& trc_;
    328 };
    329 
    330 template <typename T>
    331 void js::gc::StoreBuffer::MonoTypeBuffer<T>::trace(TenuringTracer& mover,
    332                                                   StoreBuffer* owner) {
    333  mozilla::ReentrancyGuard g(*owner);
    334  MOZ_ASSERT(owner->isEnabled());
    335 
    336  if (last_) {
    337    last_.trace(mover);
    338  }
    339 
    340  for (typename StoreSet::Range r = stores_.all(); !r.empty(); r.popFront()) {
    341    r.front().trace(mover);
    342  }
    343 }
    344 
    345 namespace js::gc {
    346 template void StoreBuffer::MonoTypeBuffer<StoreBuffer::ValueEdge>::trace(
    347    TenuringTracer&, StoreBuffer* owner);
    348 template void StoreBuffer::MonoTypeBuffer<StoreBuffer::SlotsEdge>::trace(
    349    TenuringTracer&, StoreBuffer* owner);
    350 template void StoreBuffer::MonoTypeBuffer<StoreBuffer::WasmAnyRefEdge>::trace(
    351    TenuringTracer&, StoreBuffer* owner);
    352 template struct StoreBuffer::MonoTypeBuffer<StoreBuffer::StringPtrEdge>;
    353 template struct StoreBuffer::MonoTypeBuffer<StoreBuffer::BigIntPtrEdge>;
    354 template struct StoreBuffer::MonoTypeBuffer<StoreBuffer::GetterSetterPtrEdge>;
    355 template struct StoreBuffer::MonoTypeBuffer<StoreBuffer::ObjectPtrEdge>;
    356 }  // namespace js::gc
    357 
    358 void js::gc::StoreBuffer::SlotsEdge::trace(TenuringTracer& mover) const {
    359  NativeObject* obj = object();
    360  MOZ_ASSERT(IsCellPointerValid(obj));
    361 
    362  // Beware JSObject::swap exchanging a native object for a non-native one.
    363  if (!obj->is<NativeObject>()) {
    364    return;
    365  }
    366 
    367  MOZ_ASSERT(!IsInsideNursery(obj), "obj shouldn't live in nursery.");
    368 
    369  TenuringTracer::AutoPromotedAnyToNursery promotedToNursery(mover);
    370 
    371  if (kind() == ElementKind) {
    372    uint32_t initLen = obj->getDenseInitializedLength();
    373    uint32_t numShifted = obj->getElementsHeader()->numShiftedElements();
    374    uint32_t clampedStart = start_;
    375    clampedStart = numShifted < clampedStart ? clampedStart - numShifted : 0;
    376    clampedStart = std::min(clampedStart, initLen);
    377    uint32_t clampedEnd = start_ + count_;
    378    clampedEnd = numShifted < clampedEnd ? clampedEnd - numShifted : 0;
    379    clampedEnd = std::min(clampedEnd, initLen);
    380    MOZ_ASSERT(clampedStart <= clampedEnd);
    381    auto* slotStart =
    382        static_cast<HeapSlot*>(obj->getDenseElements() + clampedStart);
    383    uint32_t nslots = clampedEnd - clampedStart;
    384    mover.traceObjectElements(slotStart->unbarrieredAddress(), nslots);
    385  } else {
    386    uint32_t start = std::min(start_, obj->slotSpan());
    387    uint32_t end = std::min(start_ + count_, obj->slotSpan());
    388    MOZ_ASSERT(start <= end);
    389    mover.traceObjectSlots(obj, start, end);
    390  }
    391 
    392  if (promotedToNursery) {
    393    mover.runtime()->gc.storeBuffer().putSlot(obj, kind(), start_, count_);
    394  }
    395 }
    396 
    397 static inline void TraceWholeCell(TenuringTracer& mover, JSObject* object) {
    398  mover.traceObject(object);
    399 }
    400 
    401 void JSDependentString::setBase(JSLinearString* newBase) {
    402  // This compiles down to a single assignment, with no type test.
    403  if (isAtomRef()) {
    404    MOZ_ASSERT(newBase->isAtom());
    405    d.s.u3.atom = &newBase->asAtom();
    406  } else {
    407    MOZ_ASSERT(newBase->canOwnDependentChars());
    408    d.s.u3.base = newBase;
    409  }
    410 
    411  if (isTenured() && !newBase->isTenured()) {
    412    MOZ_ASSERT(!InCollectedNurseryRegion(newBase));
    413    newBase->storeBuffer()->putWholeCell(this);
    414  }
    415 }
    416 
    417 static void TraceWholeCell(TenuringTracer& mover, JSString* str) {
    418  if (str->isDependent() && !str->isAtomRef()) {
    419    // For tenured dependent strings -> nursery base string edges, promote the
    420    // base immediately and then use its old chars pointer to find the offset
    421    // needed to update the dependent string's pointer if the base string moves
    422    // its chars.
    423    JSDependentString* dep = &str->asDependent();
    424    JSLinearString* base = dep->rootBaseDuringMinorGC();
    425    if (InCollectedNurseryRegion(base)) {
    426      mover.promoteOrForward(base);
    427      dep->updateToPromotedBase(base);
    428    } else {
    429      // Set base to (promoted) root base.
    430      dep->setBase(base);
    431    }
    432    return;
    433  }
    434 
    435  str->traceChildren(&mover);
    436 }
    437 
    438 static inline void TraceWholeCell(TenuringTracer& mover,
    439                                  jit::JitCode* jitcode) {
    440  jitcode->traceChildren(&mover);
    441 }
    442 
    443 template <typename T>
    444 bool TenuringTracer::traceBufferedCells(Arena* arena, ArenaCellSet* cells) {
    445  for (size_t i = 0; i < MaxArenaCellIndex; i += cells->BitsPerWord) {
    446    ArenaCellSet::WordT bitset = cells->getWord(i / cells->BitsPerWord);
    447    while (bitset) {
    448      size_t bit = i + js::detail::CountTrailingZeroes(bitset);
    449      bitset &= bitset - 1;  // Clear the low bit.
    450 
    451      auto cell =
    452          reinterpret_cast<T*>(uintptr_t(arena) + ArenaCellIndexBytes * bit);
    453 
    454      TenuringTracer::AutoPromotedAnyToNursery promotedToNursery(*this);
    455 
    456      TraceWholeCell(*this, cell);
    457 
    458      if (promotedToNursery) {
    459        runtime()->gc.storeBuffer().putWholeCell(cell);
    460      }
    461    }
    462  }
    463 
    464  return false;
    465 }
    466 
    467 bool ArenaCellSet::trace(TenuringTracer& mover) {
    468  check();
    469 
    470  arena->bufferedCells() = &ArenaCellSet::Empty;
    471 
    472  JS::TraceKind kind = MapAllocToTraceKind(arena->getAllocKind());
    473  switch (kind) {
    474    case JS::TraceKind::Object:
    475      return mover.traceBufferedCells<JSObject>(arena, this);
    476      break;
    477    case JS::TraceKind::String:
    478      return mover.traceBufferedCells<JSString>(arena, this);
    479      break;
    480    case JS::TraceKind::JitCode:
    481      return mover.traceBufferedCells<jit::JitCode>(arena, this);
    482      break;
    483    default:
    484      MOZ_CRASH("Unexpected trace kind");
    485  }
    486 }
    487 
    488 void js::gc::StoreBuffer::WholeCellBuffer::trace(TenuringTracer& mover,
    489                                                 StoreBuffer* owner) {
    490  MOZ_ASSERT(owner->isEnabled());
    491 
    492  ArenaCellSet** sweepListTail = &sweepHead_;
    493 
    494  for (LifoAlloc::Enum e(*storage_); !e.empty();) {
    495    ArenaCellSet* cellSet = e.read<ArenaCellSet>();
    496    bool needsSweep = cellSet->trace(mover);
    497    if (needsSweep) {
    498      MOZ_ASSERT(!*sweepListTail);
    499      *sweepListTail = cellSet;
    500      sweepListTail = &cellSet->next;
    501    }
    502  }
    503 }
    504 
    505 namespace js::gc {
    506 // StringRelocationOverlay assists with updating the string chars
    507 // pointers of dependent strings when their base strings are
    508 // deduplicated. It stores:
    509 //  - nursery chars of potential root base strings
    510 //  - the original pointer to the original root base (still in the nursery if it
    511 //    was originally in the nursery, even if it has been forwarded to a promoted
    512 //    string now).
    513 //
    514 // StringRelocationOverlay exploits the fact that the 3rd word of a JSString's
    515 // RelocationOverlay is not utilized and can be used to store extra information.
    516 class StringRelocationOverlay : public RelocationOverlay {
    517  union {
    518    // nursery chars of a root base
    519    const JS::Latin1Char* nurseryCharsLatin1;
    520    const char16_t* nurseryCharsTwoByte;
    521 
    522    // The nursery base can be forwarded, which becomes a string relocation
    523    // overlay, or it is not yet forwarded and is simply the (nursery) base
    524    // string.
    525    JSLinearString* nurseryBaseOrRelocOverlay;
    526 
    527    // For ropes. Present only to simplify the generated code.
    528    JSString* unusedLeftChild;
    529  };
    530 
    531 public:
    532  StringRelocationOverlay(Cell* dst, const JS::Latin1Char* chars)
    533      : RelocationOverlay(dst), nurseryCharsLatin1(chars) {}
    534 
    535  StringRelocationOverlay(Cell* dst, const char16_t* chars)
    536      : RelocationOverlay(dst), nurseryCharsTwoByte(chars) {}
    537 
    538  StringRelocationOverlay(Cell* dst, JSLinearString* origBase)
    539      : RelocationOverlay(dst), nurseryBaseOrRelocOverlay(origBase) {}
    540 
    541  StringRelocationOverlay(Cell* dst, JSString* origLeftChild)
    542      : RelocationOverlay(dst), unusedLeftChild(origLeftChild) {}
    543 
    544  static const StringRelocationOverlay* fromCell(const Cell* cell) {
    545    return static_cast<const StringRelocationOverlay*>(cell);
    546  }
    547 
    548  static StringRelocationOverlay* fromCell(Cell* cell) {
    549    return static_cast<StringRelocationOverlay*>(cell);
    550  }
    551 
    552  void setNext(StringRelocationOverlay* next) {
    553    RelocationOverlay::setNext(next);
    554  }
    555 
    556  StringRelocationOverlay* next() const {
    557    MOZ_ASSERT(isForwarded());
    558    return (StringRelocationOverlay*)next_;
    559  }
    560 
    561  template <typename CharT>
    562  MOZ_ALWAYS_INLINE const CharT* savedNurseryChars() const {
    563    if constexpr (std::is_same_v<CharT, JS::Latin1Char>) {
    564      return savedNurseryCharsLatin1();
    565    } else {
    566      return savedNurseryCharsTwoByte();
    567    }
    568  }
    569 
    570  const MOZ_ALWAYS_INLINE JS::Latin1Char* savedNurseryCharsLatin1() const {
    571    MOZ_ASSERT(!forwardingAddress()->as<JSString>()->hasBase());
    572    return nurseryCharsLatin1;
    573  }
    574 
    575  const MOZ_ALWAYS_INLINE char16_t* savedNurseryCharsTwoByte() const {
    576    MOZ_ASSERT(!forwardingAddress()->as<JSString>()->hasBase());
    577    return nurseryCharsTwoByte;
    578  }
    579 
    580  JSLinearString* savedNurseryBaseOrRelocOverlay() const {
    581    MOZ_ASSERT(forwardingAddress()->as<JSString>()->hasBase());
    582    return nurseryBaseOrRelocOverlay;
    583  }
    584 
    585  // Transform a nursery string to a StringRelocationOverlay that is forwarded
    586  // to a promoted string.
    587  inline static StringRelocationOverlay* forwardDependentString(JSString* src,
    588                                                                Cell* dst);
    589 
    590  // Usually only called on non-dependent strings, except for the case where a
    591  // dependent string is converted to a linear string.
    592  static StringRelocationOverlay* forwardString(JSString* src, Cell* dst) {
    593    MOZ_ASSERT(!src->isForwarded());
    594    MOZ_ASSERT(!dst->isForwarded());
    595 
    596    JS::AutoCheckCannotGC nogc;
    597 
    598    // Initialize the overlay for a non-dependent string (that could be the root
    599    // base of other strings), remember nursery non-inlined chars.
    600    //
    601    // Note that this will store the chars pointer even when it is known that it
    602    // will never be used (!canOwnDependentChar()), or a left child pointer of
    603    // a rope that will never get used, in order to simplify the generated code
    604    // to do an unconditional store.
    605    //
    606    // All of these compile down to
    607    //    header_.value_ = dst | 1; /* offset 0 */
    608    //    StringRelocationOverlay.union = d.s.u2; /* offset 16 <- offset 8 */
    609    if (src->isLinear()) {
    610      if (src->hasTwoByteChars()) {
    611        auto* nurseryCharsTwoByte = src->asLinear().twoByteChars(nogc);
    612        return new (src) StringRelocationOverlay(dst, nurseryCharsTwoByte);
    613      }
    614      auto* nurseryCharsLatin1 = src->asLinear().latin1Chars(nogc);
    615      return new (src) StringRelocationOverlay(dst, nurseryCharsLatin1);
    616    } else {
    617      return new (src) StringRelocationOverlay(
    618          dst, dst->as<JSString>()->asRope().leftChild());
    619    }
    620  }
    621 };
    622 
    623 /* static */
    624 StringRelocationOverlay* StringRelocationOverlay::forwardDependentString(
    625    JSString* src, Cell* dst) {
    626  MOZ_ASSERT(src->isDependent());
    627  MOZ_ASSERT(!src->isForwarded());
    628  MOZ_ASSERT(!dst->isForwarded());
    629  JSLinearString* origBase = src->asDependent().rootBaseDuringMinorGC();
    630  return new (src) StringRelocationOverlay(dst, origBase);
    631 }
    632 
    633 }  // namespace js::gc
    634 
    635 JSLinearString* JSDependentString::rootBaseDuringMinorGC() {
    636  JSLinearString* root = this;
    637  while (MaybeForwarded(root)->hasBase()) {
    638    if (root->isForwarded()) {
    639      root = js::gc::StringRelocationOverlay::fromCell(root)
    640                 ->savedNurseryBaseOrRelocOverlay();
    641    } else {
    642      // Possibly nursery or tenured string (not an overlay).
    643      root = root->nurseryBaseOrRelocOverlay();
    644    }
    645  }
    646  return root;
    647 }
    648 
    649 template <typename CharT>
    650 static bool PtrIsWithinRange(const CharT* ptr,
    651                             const mozilla::Range<const CharT>& valid) {
    652  return size_t(ptr - valid.begin().get()) <= valid.length();
    653 }
    654 
    655 /* static */
    656 template <typename CharT>
    657 void JSLinearString::maybeCloneCharsOnPromotionTyped(JSLinearString* str) {
    658  MOZ_ASSERT(!InCollectedNurseryRegion(str), "str should have been promoted");
    659  MOZ_ASSERT(str->isDependent());
    660  JSLinearString* root = str->asDependent().rootBaseDuringMinorGC();
    661  JS::AutoCheckCannotGC nogc;
    662  const CharT* chars = str->chars<CharT>(nogc);
    663 
    664  // If a dependent string is using a small percentage of its base string's
    665  // data, and it is not (yet) known whether anything else might be keeping
    666  // that base string alive, then clone the chars (and avoid marking the base)
    667  // in order to hopefully allow the base to be freed (assuming nothing later
    668  // during marking needs the base for other reasons).
    669  //
    670  // "Nothing else is yet known to keep the base alive" == "the base is not
    671  // currently forwarded".
    672  bool baseKnownLiveYet = IsForwarded(root);
    673  bool cloneToSaveSpace =
    674      !baseKnownLiveYet &&
    675      JSDependentString::smallComparedToBase(str->length(), root->length());
    676 
    677  if (!cloneToSaveSpace) {
    678    // If the root base (going through the nursery) is going to be collected,
    679    // then it will record enough information for this dependent string's chars
    680    // to be updated.
    681    if (InCollectedNurseryRegion(root)) {
    682      return;  // Remain dependent.
    683    }
    684 
    685    // If the base has not moved its chars, continue using them.
    686    if (PtrIsWithinRange(chars, root->range<CharT>(nogc))) {
    687      return;  // Remain dependent.
    688    }
    689 
    690    // Must clone for correctness. The reachable root base string has already
    691    // been promoted (and so can't store information needed for fixup) and the
    692    // dependent string uses chars from somewhere else. Clone the chars before
    693    // the minor GC ends and frees or reuses them.
    694  }
    695 
    696  // Clone the chars.
    697  js::AutoEnterOOMUnsafeRegion oomUnsafe;
    698  size_t len = str->length();
    699  size_t nbytes = len * sizeof(CharT);
    700  CharT* data =
    701      str->zone()->pod_arena_malloc<CharT>(js::StringBufferArena, len);
    702  if (!data) {
    703    oomUnsafe.crash("cloning at-risk dependent string");
    704  }
    705  js_memcpy(data, chars, nbytes);
    706 
    707  // Overwrite the dest string with a new linear string.
    708  new (str) JSLinearString(data, len, false /* hasBuffer */);
    709  if (str->isTenured()) {
    710    str->zone()->addCellMemory(str, nbytes, js::MemoryUse::StringContents);
    711  } else {
    712    AutoEnterOOMUnsafeRegion oomUnsafe;
    713    JSRuntime* rt = str->runtimeFromAnyThread();
    714    if (!rt->gc.nursery().registerMallocedBuffer(data, nbytes)) {
    715      oomUnsafe.crash("maybeCloneCharsOnPromotionTyped");
    716    }
    717  }
    718 }
    719 
    720 template void JSLinearString::maybeCloneCharsOnPromotionTyped<JS::Latin1Char>(
    721    JSLinearString* str);
    722 template void JSLinearString::maybeCloneCharsOnPromotionTyped<char16_t>(
    723    JSLinearString* str);
    724 
    725 // Update a promoted dependent string with a nursery base. The base chain will
    726 // have been collapsed to a single link, so only the simple case of a promoted
    727 // dependent string with a nursery base needs to be considered.
    728 template <typename CharT>
    729 void JSDependentString::updateToPromotedBaseImpl(JSLinearString* base) {
    730  MOZ_ASSERT(!InCollectedNurseryRegion(this));
    731  MOZ_ASSERT(IsInsideNursery(base));
    732  MOZ_ASSERT(!Forwarded(base)->hasBase(), "base chain should be collapsed");
    733  MOZ_ASSERT(base->isForwarded(), "root base should be kept alive");
    734  auto* baseOverlay = js::gc::StringRelocationOverlay::fromCell(base);
    735  const CharT* oldBaseChars = baseOverlay->savedNurseryChars<CharT>();
    736 
    737  // We have the base's original chars pointer and its current chars pointer.
    738  // Update our chars pointer, which is an offset from the original base
    739  // chars, and make it point to the same offset within the root's chars.
    740  // (Most of the time, the base chars didn't move and so this has no
    741  // effect.)
    742  const CharT* oldChars = JSString::nonInlineCharsRaw<CharT>();
    743  size_t offset = oldChars - oldBaseChars;
    744  JSLinearString* promotedBase = Forwarded(base);
    745  MOZ_ASSERT(offset < promotedBase->length());
    746 
    747  const CharT* newBaseChars =
    748      promotedBase->JSString::nonInlineCharsRaw<CharT>();
    749  relocateBaseAndChars(promotedBase, newBaseChars, offset);
    750 }
    751 
    752 inline void JSDependentString::updateToPromotedBase(JSLinearString* base) {
    753  // The base should have been traced.
    754  MOZ_ASSERT(base->isForwarded() || !InCollectedNurseryRegion(base));
    755 
    756  if (hasTwoByteChars()) {
    757    updateToPromotedBaseImpl<char16_t>(base);
    758  } else {
    759    updateToPromotedBaseImpl<JS::Latin1Char>(base);
    760  }
    761 }
    762 
    763 template <typename T>
    764 void js::gc::StoreBuffer::CellPtrEdge<T>::trace(TenuringTracer& mover) const {
    765  static_assert(std::is_base_of_v<Cell, T>, "T must be a Cell type");
    766  static_assert(!GCTypeIsTenured<T>(), "T must not be a tenured Cell type");
    767 
    768  T* thing = *edge;
    769  if (!thing) {
    770    return;
    771  }
    772 
    773  MOZ_ASSERT(IsCellPointerValid(thing));
    774  MOZ_ASSERT(thing->getTraceKind() == JS::MapTypeToTraceKind<T>::kind);
    775 
    776  if (std::is_same_v<JSString, T>) {
    777    // Nursery string deduplication requires all tenured string -> nursery
    778    // string edges to be registered with the whole cell buffer in order to
    779    // correctly set the non-deduplicatable bit.
    780    MOZ_ASSERT(!mover.runtime()->gc.isPointerWithinTenuredCell(
    781        edge, JS::TraceKind::String));
    782  }
    783 
    784  if (!mover.nursery().inCollectedRegion(thing)) {
    785    return;
    786  }
    787 
    788  *edge = mover.promoteOrForward(thing);
    789 
    790  if (IsInsideNursery(*edge)) {
    791    mover.runtime()->gc.storeBuffer().putCell(edge);
    792  }
    793 }
    794 
    795 void js::gc::StoreBuffer::ValueEdge::trace(TenuringTracer& mover) const {
    796  if (!isGCThing()) {
    797    return;
    798  }
    799 
    800  TenuringTracer::AutoPromotedAnyToNursery promotedToNursery(mover);
    801 
    802  mover.traverse(edge);
    803 
    804  if (promotedToNursery) {
    805    mover.runtime()->gc.storeBuffer().putValue(edge);
    806  }
    807 }
    808 
    809 void js::gc::StoreBuffer::WasmAnyRefEdge::trace(TenuringTracer& mover) const {
    810  if (!isGCThing()) {
    811    return;
    812  }
    813 
    814  TenuringTracer::AutoPromotedAnyToNursery promotedToNursery(mover);
    815 
    816  mover.traverse(edge);
    817 
    818  if (promotedToNursery) {
    819    mover.runtime()->gc.storeBuffer().putWasmAnyRef(edge);
    820  }
    821 }
    822 
    823 // Visit all object children of the object and trace them.
    824 void js::gc::TenuringTracer::traceObject(JSObject* obj) {
    825  const JSClass* clasp = obj->getClass();
    826  MOZ_ASSERT(clasp);
    827 
    828  if (clasp->hasTrace()) {
    829    clasp->doTrace(this, obj);
    830  }
    831 
    832  if (!obj->is<NativeObject>()) {
    833    return;
    834  }
    835 
    836  NativeObject* nobj = &obj->as<NativeObject>();
    837  if (!nobj->hasEmptyElements()) {
    838    HeapSlotArray elements = nobj->getDenseElements();
    839    Value* elems = elements.begin()->unbarrieredAddress();
    840    traceObjectElements(elems, nobj->getDenseInitializedLength());
    841  }
    842 
    843  traceObjectSlots(nobj, 0, nobj->slotSpan());
    844 }
    845 
    846 void js::gc::TenuringTracer::traceObjectSlots(NativeObject* nobj,
    847                                              uint32_t start, uint32_t end) {
    848  auto traceRange = [this](HeapSlot* slotStart, HeapSlot* slotEnd) {
    849    traceSlots(slotStart->unbarrieredAddress(), slotEnd->unbarrieredAddress());
    850  };
    851  nobj->forEachSlotRange(start, end, traceRange);
    852 }
    853 
    854 void js::gc::TenuringTracer::traceObjectElements(JS::Value* vp,
    855                                                 uint32_t count) {
    856  traceSlots(vp, vp + count);
    857 }
    858 
    859 void js::gc::TenuringTracer::traceSlots(Value* vp, Value* end) {
    860  for (; vp != end; ++vp) {
    861    traverse(vp);
    862  }
    863 }
    864 
    865 void js::gc::TenuringTracer::traceString(JSString* str) {
    866  AutoPromotedAnyToNursery promotedToNursery(*this);
    867  str->traceChildren(this);
    868  if (str->isTenured() && promotedToNursery) {
    869    runtime()->gc.storeBuffer().putWholeCell(str);
    870  }
    871 }
    872 
    873 #ifdef DEBUG
    874 static inline uintptr_t OffsetFromChunkStart(void* p) {
    875  return uintptr_t(p) & gc::ChunkMask;
    876 }
    877 static inline size_t OffsetToChunkEnd(void* p) {
    878  uintptr_t offsetFromStart = OffsetFromChunkStart(p);
    879  MOZ_ASSERT(offsetFromStart < ChunkSize);
    880  return ChunkSize - offsetFromStart;
    881 }
    882 #endif
    883 
    884 /* Insert the given relocation entry into the list of things to visit. */
    885 inline void js::gc::TenuringTracer::insertIntoObjectFixupList(
    886    RelocationOverlay* entry) {
    887  entry->setNext(objHead);
    888  objHead = entry;
    889 }
    890 
    891 template <typename T>
    892 inline T* js::gc::TenuringTracer::alloc(Zone* zone, AllocKind kind, Cell* src) {
    893  AllocSite* site = NurseryCellHeader::from(src)->allocSite();
    894  site->incPromotedCount();
    895 
    896  void* ptr = allocCell<T::TraceKind>(zone, kind, site, src);
    897  auto* cell = reinterpret_cast<T*>(ptr);
    898  if (IsInsideNursery(cell)) {
    899    MOZ_ASSERT(!nursery().inCollectedRegion(cell));
    900    promotedToNursery = true;
    901  }
    902 
    903  return cell;
    904 }
    905 
    906 template <JS::TraceKind traceKind>
    907 void* js::gc::TenuringTracer::allocCell(Zone* zone, AllocKind allocKind,
    908                                        AllocSite* site, Cell* src) {
    909  MOZ_ASSERT(zone == src->zone());
    910 
    911  if (!shouldTenure(zone, traceKind, src)) {
    912    // Allocations from the optimized alloc site continue to use that site,
    913    // otherwise a special promoted alloc site it used.
    914    if (site->kind() != AllocSite::Kind::Optimized) {
    915      site = &zone->pretenuring.promotedAllocSite(traceKind);
    916    }
    917 
    918    size_t thingSize = Arena::thingSize(allocKind);
    919    void* ptr = nursery_.tryAllocateCell(site, thingSize, traceKind);
    920    if (MOZ_LIKELY(ptr)) {
    921      return ptr;
    922    }
    923 
    924    JSContext* cx = runtime()->mainContextFromOwnThread();
    925    ptr = CellAllocator::RetryNurseryAlloc<NoGC>(cx, traceKind, allocKind,
    926                                                 thingSize, site);
    927    if (MOZ_LIKELY(ptr)) {
    928      return ptr;
    929    }
    930 
    931    // The nursery is full. This is unlikely but can happen. Fall through to
    932    // the tenured allocation path.
    933  }
    934 
    935  return AllocateTenuredCellInGC(zone, allocKind);
    936 }
    937 
    938 JSString* js::gc::TenuringTracer::allocString(JSString* src, Zone* zone,
    939                                              AllocKind dstKind) {
    940  JSString* dst = alloc<JSString>(zone, dstKind, src);
    941  promotedSize += moveString(dst, src, dstKind);
    942  promotedCells++;
    943 
    944  return dst;
    945 }
    946 
    947 bool js::gc::TenuringTracer::shouldTenure(Zone* zone, JS::TraceKind traceKind,
    948                                          Cell* cell) {
    949  return tenureEverything || !zone->allocKindInNursery(traceKind) ||
    950         nursery_.shouldTenure(cell);
    951 }
    952 
    953 JSObject* js::gc::TenuringTracer::promoteObjectSlow(JSObject* src) {
    954  MOZ_ASSERT(IsInsideNursery(src));
    955  MOZ_ASSERT(!src->is<PlainObject>());
    956 
    957  AllocKind dstKind = src->allocKindForTenure(nursery());
    958  auto* dst = alloc<JSObject>(src->nurseryZone(), dstKind, src);
    959 
    960  size_t srcSize = Arena::thingSize(dstKind);
    961 
    962  // Arrays do not necessarily have the same AllocKind between src and dst. We
    963  // deal with this by copying elements manually, possibly re-inlining them if
    964  // there is adequate room inline in dst.
    965  //
    966  // For Arrays we're reducing promotedSize to the smaller srcSize because
    967  // moveElements() accounts for all Array elements, even if they are inlined.
    968  if (src->is<ArrayObject>()) {
    969    srcSize = sizeof(NativeObject);
    970  } else if (src->is<FixedLengthTypedArrayObject>()) {
    971    auto* tarray = &src->as<FixedLengthTypedArrayObject>();
    972    // Typed arrays with inline data do not necessarily have the same
    973    // AllocKind between src and dst. The nursery does not allocate an
    974    // inline data buffer that has the same size as the slow path will do.
    975    // In the slow path, the Typed Array Object stores the inline data
    976    // in the allocated space that fits the AllocKind. In the fast path,
    977    // the nursery will allocate another buffer that is directly behind the
    978    // minimal JSObject. That buffer size plus the JSObject size is not
    979    // necessarily as large as the slow path's AllocKind size.
    980    if (tarray->hasInlineElements()) {
    981      AllocKind srcKind =
    982          GetGCObjectKind(FixedLengthTypedArrayObject::FIXED_DATA_START);
    983      size_t headerSize = Arena::thingSize(srcKind);
    984      srcSize = headerSize + tarray->byteLength();
    985    }
    986  }
    987 
    988  promotedSize += srcSize;
    989  promotedCells++;
    990 
    991  // Copy the Cell contents.
    992  MOZ_ASSERT(OffsetFromChunkStart(src) >= sizeof(ChunkBase));
    993  MOZ_ASSERT(OffsetToChunkEnd(src) >= srcSize);
    994  js_memcpy(dst, src, srcSize);
    995 
    996  // Move the slots and elements, if we need to.
    997  if (src->is<NativeObject>()) {
    998    NativeObject* ndst = &dst->as<NativeObject>();
    999    NativeObject* nsrc = &src->as<NativeObject>();
   1000    promotedSize += moveSlots(ndst, nsrc);
   1001    promotedSize += moveElements(ndst, nsrc, dstKind);
   1002  }
   1003 
   1004  JSObjectMovedOp op = dst->getClass()->extObjectMovedOp();
   1005  MOZ_ASSERT_IF(src->is<ProxyObject>(), op == proxy_ObjectMoved);
   1006  if (op) {
   1007    // Tell the hazard analysis that the object moved hook can't GC.
   1008    JS::AutoSuppressGCAnalysis nogc;
   1009    promotedSize += op(dst, src);
   1010  } else {
   1011    MOZ_ASSERT_IF(src->getClass()->hasFinalize(),
   1012                  CanNurseryAllocateFinalizedClass(src->getClass()));
   1013  }
   1014 
   1015  RelocationOverlay* overlay = RelocationOverlay::forwardCell(src, dst);
   1016  insertIntoObjectFixupList(overlay);
   1017 
   1018  gcprobes::PromoteToTenured(src, dst);
   1019  return dst;
   1020 }
   1021 
   1022 inline JSObject* js::gc::TenuringTracer::promotePlainObject(PlainObject* src) {
   1023  // Fast path version of promoteObjectSlow() for specialized for PlainObject.
   1024 
   1025  MOZ_ASSERT(IsInsideNursery(src));
   1026 
   1027  AllocKind dstKind = src->allocKindForTenure();
   1028  auto* dst = alloc<PlainObject>(src->nurseryZone(), dstKind, src);
   1029 
   1030  size_t srcSize = Arena::thingSize(dstKind);
   1031  promotedSize += srcSize;
   1032  promotedCells++;
   1033 
   1034  // Copy the Cell contents.
   1035  MOZ_ASSERT(OffsetFromChunkStart(src) >= sizeof(ChunkBase));
   1036  MOZ_ASSERT(OffsetToChunkEnd(src) >= srcSize);
   1037  js_memcpy(dst, src, srcSize);
   1038 
   1039  // Move the slots and elements.
   1040  promotedSize += moveSlots(dst, src);
   1041  promotedSize += moveElements(dst, src, dstKind);
   1042 
   1043  MOZ_ASSERT(!dst->getClass()->extObjectMovedOp());
   1044 
   1045  RelocationOverlay* overlay = RelocationOverlay::forwardCell(src, dst);
   1046  insertIntoObjectFixupList(overlay);
   1047 
   1048  gcprobes::PromoteToTenured(src, dst);
   1049  return dst;
   1050 }
   1051 
   1052 size_t js::gc::TenuringTracer::moveSlots(NativeObject* dst, NativeObject* src) {
   1053  /* Fixed slots have already been copied over. */
   1054  if (!src->hasDynamicSlots()) {
   1055    return 0;
   1056  }
   1057 
   1058  size_t count = src->numDynamicSlots();
   1059  size_t allocSize = ObjectSlots::allocSize(count);
   1060 
   1061  ObjectSlots* header = src->getSlotsHeader();
   1062  Nursery::WasBufferMoved result =
   1063      nursery().maybeMoveBufferOnPromotion(&header, dst, allocSize);
   1064  if (result == Nursery::BufferNotMoved) {
   1065    return 0;
   1066  }
   1067 
   1068  dst->slots_ = header->slots();
   1069  if (count) {
   1070    nursery().setSlotsForwardingPointer(src->slots_, dst->slots_, count);
   1071  }
   1072  return allocSize;
   1073 }
   1074 
   1075 size_t js::gc::TenuringTracer::moveElements(NativeObject* dst,
   1076                                            NativeObject* src,
   1077                                            AllocKind dstKind) {
   1078  if (src->hasEmptyElements()) {
   1079    return 0;
   1080  }
   1081 
   1082  ObjectElements* srcHeader = src->getElementsHeader();
   1083  size_t nslots = srcHeader->numAllocatedElements();
   1084  size_t allocSize = nslots * sizeof(HeapSlot);
   1085 
   1086  // Shifted elements are copied too.
   1087  uint32_t numShifted = srcHeader->numShiftedElements();
   1088 
   1089  void* unshiftedHeader = src->getUnshiftedElementsHeader();
   1090 
   1091  /* Unlike other objects, Arrays can have fixed elements. */
   1092  if (src->is<ArrayObject>() && nslots <= GetGCKindSlots(dstKind)) {
   1093    dst->as<NativeObject>().setFixedElements();
   1094    js_memcpy(dst->getElementsHeader(), unshiftedHeader, allocSize);
   1095    dst->elements_ += numShifted;
   1096    dst->getElementsHeader()->flags |= ObjectElements::FIXED;
   1097    nursery().setElementsForwardingPointer(srcHeader, dst->getElementsHeader(),
   1098                                           srcHeader->capacity);
   1099    return allocSize;
   1100  }
   1101 
   1102  /* TODO Bug 874151: Prefer to put element data inline if we have space. */
   1103 
   1104  Nursery::WasBufferMoved result =
   1105      nursery().maybeMoveBufferOnPromotion(&unshiftedHeader, dst, allocSize);
   1106  if (result == Nursery::BufferNotMoved) {
   1107    return 0;
   1108  }
   1109 
   1110  dst->elements_ =
   1111      static_cast<ObjectElements*>(unshiftedHeader)->elements() + numShifted;
   1112  dst->getElementsHeader()->flags &= ~ObjectElements::FIXED;
   1113  nursery().setElementsForwardingPointer(srcHeader, dst->getElementsHeader(),
   1114                                         srcHeader->capacity);
   1115  return allocSize;
   1116 }
   1117 
   1118 inline void js::gc::TenuringTracer::insertIntoStringFixupList(
   1119    StringRelocationOverlay* entry) {
   1120  entry->setNext(stringHead);
   1121  stringHead = entry;
   1122 }
   1123 
   1124 JSString* js::gc::TenuringTracer::promoteString(JSString* src) {
   1125  MOZ_ASSERT(IsInsideNursery(src));
   1126  MOZ_ASSERT(!src->isForwarded());
   1127  MOZ_ASSERT(!src->isExternal());
   1128 
   1129 #ifdef JS_GC_ZEAL
   1130  if (promotionStats) {
   1131    promotionStats->notePromotedString(src);
   1132  }
   1133 #endif
   1134 
   1135  AllocKind dstKind = src->getAllocKind();
   1136  Zone* zone = src->nurseryZone();
   1137 
   1138  // If this string is in the StringToAtomCache, try to deduplicate it by using
   1139  // the atom. Don't do this for dependent strings because they're more
   1140  // complicated. See StringRelocationOverlay and DeduplicationStringHasher
   1141  // comments.
   1142  MOZ_ASSERT(!src->isAtom());
   1143  if (src->isLinear() && src->inStringToAtomCache() &&
   1144      src->isDeduplicatable() && !src->hasBase()) {
   1145    JSLinearString* linear = &src->asLinear();
   1146    JSAtom* atom = runtime()->caches().stringToAtomCache.lookupInMap(linear);
   1147    // The string will not be present in the cache if it was previously promoted
   1148    // to the second nursery generation.
   1149    if (atom) {
   1150      // Only deduplicate if both strings have the same encoding, to not confuse
   1151      // dependent strings.
   1152      if (src->hasTwoByteChars() == atom->hasTwoByteChars()) {
   1153        // The StringToAtomCache isn't used for inline strings (due to the
   1154        // minimum length) so canOwnDependentChars must be true for both src and
   1155        // atom. This means if there are dependent strings floating around using
   1156        // str's chars, they will be able to use the chars from the atom.
   1157        static_assert(StringToAtomCache::MinStringLength >
   1158                      JSFatInlineString::MAX_LENGTH_LATIN1);
   1159        static_assert(StringToAtomCache::MinStringLength >
   1160                      JSFatInlineString::MAX_LENGTH_TWO_BYTE);
   1161        MOZ_ASSERT(src->canOwnDependentChars());
   1162        MOZ_ASSERT(atom->canOwnDependentChars());
   1163 
   1164        StringRelocationOverlay::forwardString(src, atom);
   1165        gcprobes::PromoteToTenured(src, atom);
   1166        return atom;
   1167      }
   1168    }
   1169  }
   1170 
   1171  JSString* dst;
   1172 
   1173  // A live nursery string can only get deduplicated when:
   1174  // 1. Its length is smaller than MAX_DEDUPLICATABLE_STRING_LENGTH:
   1175  //    Hashing a long string can affect performance.
   1176  // 2. It is linear:
   1177  //    Deduplicating every node in it would end up doing O(n^2) hashing work.
   1178  // 3. It is deduplicatable:
   1179  //    The JSString NON_DEDUP_BIT flag is unset.
   1180  // 4. It matches an entry in stringDeDupSet.
   1181  // 5. It is moved to the tenured heap.
   1182 
   1183  if (shouldTenure(zone, JS::TraceKind::String, src) &&
   1184      src->length() < MAX_DEDUPLICATABLE_STRING_LENGTH && src->isLinear() &&
   1185      src->isDeduplicatable() && stringDeDupSet.isSome()) {
   1186    src->clearBitsOnTenure();
   1187    auto p = stringDeDupSet->lookupForAdd(src);
   1188    if (p) {
   1189      // Deduplicate to the looked-up string!
   1190      dst = *p;
   1191      MOZ_ASSERT(dst->isTenured());  // Never deduplicate to a from-space cell.
   1192      zone->stringStats.ref().noteDeduplicated(src->length(), src->allocSize());
   1193      if (src->isDependent()) {
   1194        StringRelocationOverlay::forwardDependentString(&src->asDependent(),
   1195                                                        dst);
   1196      } else {
   1197        StringRelocationOverlay::forwardString(src, dst);
   1198      }
   1199      gcprobes::PromoteToTenured(src, dst);
   1200      return dst;
   1201    }
   1202 
   1203    dst = allocString(src, zone, dstKind);
   1204 
   1205    // In some situations, a string may be converted to a different type when
   1206    // tenured. Currently, this only happens when a dependent string's chain of
   1207    // base strings makes it impossible to recover its data, in which case it
   1208    // will get converted to a regular linear string. In order to avoid
   1209    // rehashing and some complexity, do not deduplicate to such strings.
   1210    if (dst->flags() == src->flags()) {
   1211      using DedupHasher [[maybe_unused]] = DeduplicationStringHasher<JSString*>;
   1212      MOZ_ASSERT(DedupHasher::hash(src) == DedupHasher::hash(dst),
   1213                 "src and dst must have the same hash for lookupForAdd");
   1214 
   1215      if (!stringDeDupSet->add(p, dst)) {
   1216        // When there is oom caused by the stringDeDupSet, stop deduplicating
   1217        // strings.
   1218        stringDeDupSet.reset();
   1219      }
   1220    }
   1221  } else {
   1222    dst = allocString(src, zone, dstKind);
   1223    if (dst->isTenured()) {
   1224      src->clearBitsOnTenure();
   1225      dst->clearBitsOnTenure();
   1226    }
   1227  }
   1228 
   1229  gcprobes::PromoteToTenured(src, dst);
   1230  zone->stringStats.ref().noteTenured(src->allocSize());
   1231 
   1232  if (dst->isDependent()) {
   1233    // Dependent string:
   1234    //   - root base was tenured => done
   1235    //   - otherwise => promote the root base if it has not already been
   1236    //     promoted, then update the dependent string's chars pointer based on
   1237    //     the root base's original chars pointer (stored in its
   1238    //     StringRelocationOverlay during promotion)
   1239 
   1240    JSLinearString* base = src->asDependent().rootBaseDuringMinorGC();
   1241 
   1242    // Limited recursion: the root base cannot be dependent, so this will not
   1243    // recurse again.
   1244    JSString* promotedBase =
   1245        InCollectedNurseryRegion(base) ? promoteOrForward(base) : base;
   1246    MOZ_ASSERT(!promotedBase->isDependent());
   1247 
   1248    dst->asDependent().setBase(&promotedBase->asLinear());
   1249    if (base != promotedBase) {
   1250      dst->asDependent().updateToPromotedBase(base);
   1251    }
   1252 
   1253    StringRelocationOverlay::forwardDependentString(src, dst);
   1254  } else {
   1255    // Non-dependent string: store the original chars pointer in the nursery
   1256    // cell (for future promotions of any dependent strings that use this string
   1257    // as a base), then forward to the promoted cell.
   1258 
   1259    StringRelocationOverlay::forwardString(src, dst);
   1260    if (dst->isRope()) {
   1261      // Enqueue for recursion through the children.
   1262      insertIntoStringFixupList(StringRelocationOverlay::fromCell(src));
   1263    }
   1264  }
   1265 
   1266  MOZ_ASSERT_IF(dst->isTenured() && dst->isLinear(), dst->isDeduplicatable());
   1267 
   1268  return dst;
   1269 }
   1270 
   1271 JS::BigInt* js::gc::TenuringTracer::promoteBigInt(JS::BigInt* src) {
   1272  MOZ_ASSERT(IsInsideNursery(src));
   1273  MOZ_ASSERT(!src->isForwarded());
   1274 
   1275 #ifdef JS_GC_ZEAL
   1276  if (promotionStats) {
   1277    promotionStats->notePromotedBigInt(src);
   1278  }
   1279 #endif
   1280 
   1281  AllocKind dstKind = src->getAllocKind();
   1282  Zone* zone = src->nurseryZone();
   1283 
   1284  JS::BigInt* dst = alloc<JS::BigInt>(zone, dstKind, src);
   1285  promotedSize += moveBigInt(dst, src, dstKind);
   1286  promotedCells++;
   1287 
   1288  RelocationOverlay::forwardCell(src, dst);
   1289 
   1290  gcprobes::PromoteToTenured(src, dst);
   1291  return dst;
   1292 }
   1293 
   1294 GetterSetter* js::gc::TenuringTracer::promoteGetterSetter(GetterSetter* src) {
   1295  MOZ_ASSERT(IsInsideNursery(src));
   1296  MOZ_ASSERT(!src->isForwarded());
   1297 
   1298 #ifdef JS_GC_ZEAL
   1299  if (promotionStats) {
   1300    promotionStats->notePromotedGetterSetter(src);
   1301  }
   1302 #endif
   1303 
   1304  AllocKind dstKind = AllocKind::GETTER_SETTER;
   1305  MOZ_ASSERT(src->getAllocKind() == dstKind);
   1306  Zone* zone = src->nurseryZone();
   1307 
   1308  GetterSetter* dst = alloc<GetterSetter>(zone, dstKind, src);
   1309  promotedSize += moveGetterSetter(dst, src, dstKind);
   1310  promotedCells++;
   1311 
   1312  RelocationOverlay::forwardCell(src, dst);
   1313  gcprobes::PromoteToTenured(src, dst);
   1314 
   1315  // GetterSetter only has pointers to the getter/setter JSObjects. We can trace
   1316  // those directly without using a fixup list. AutoPromotedAnyToNursery will
   1317  // reset promotedToNursery to false so we save/restore the current value.
   1318  bool promotedToNurseryPrev = promotedToNursery;
   1319  {
   1320    AutoPromotedAnyToNursery promotedAnyToNursery(*this);
   1321    dst->traceChildren(this);
   1322    if (dst->isTenured() && promotedAnyToNursery) {
   1323      runtime()->gc.storeBuffer().putWholeCell(dst);
   1324    }
   1325  }
   1326  promotedToNursery = promotedToNurseryPrev;
   1327 
   1328  return dst;
   1329 }
   1330 
   1331 void js::gc::TenuringTracer::collectToObjectFixedPoint() {
   1332  while (RelocationOverlay* p = objHead) {
   1333    MOZ_ASSERT(nursery().inCollectedRegion(p));
   1334    objHead = objHead->next();
   1335    auto* obj = static_cast<JSObject*>(p->forwardingAddress());
   1336 
   1337    MOZ_ASSERT_IF(IsInsideNursery(obj), !nursery().inCollectedRegion(obj));
   1338 
   1339    AutoPromotedAnyToNursery promotedAnyToNursery(*this);
   1340    traceObject(obj);
   1341    if (obj->isTenured() && promotedAnyToNursery) {
   1342      runtime()->gc.storeBuffer().putWholeCell(obj);
   1343    }
   1344  }
   1345 }
   1346 
   1347 void js::gc::TenuringTracer::collectToStringFixedPoint() {
   1348  // Recurse through ropes.
   1349 
   1350  while (StringRelocationOverlay* p = stringHead) {
   1351    MOZ_ASSERT(nursery().inCollectedRegion(p));
   1352    stringHead = stringHead->next();
   1353 
   1354    auto* promotedStr = static_cast<JSString*>(p->forwardingAddress());
   1355    MOZ_ASSERT_IF(IsInsideNursery(promotedStr),
   1356                  !nursery().inCollectedRegion(promotedStr));
   1357 
   1358    // To ensure the NON_DEDUP_BIT was reset properly.
   1359    MOZ_ASSERT(!promotedStr->isAtom());
   1360    MOZ_ASSERT_IF(promotedStr->isTenured() && promotedStr->isLinear(),
   1361                  promotedStr->isDeduplicatable());
   1362 
   1363    MOZ_ASSERT(promotedStr->isRope());
   1364    traceString(promotedStr);
   1365  }
   1366 }
   1367 
   1368 size_t js::gc::TenuringTracer::moveString(JSString* dst, JSString* src,
   1369                                          AllocKind dstKind) {
   1370  size_t size = Arena::thingSize(dstKind);
   1371 
   1372  MOZ_ASSERT_IF(dst->isTenured(),
   1373                dst->asTenured().getAllocKind() == src->getAllocKind());
   1374 
   1375  // Copy the Cell contents.
   1376  MOZ_ASSERT(OffsetToChunkEnd(src) >= size);
   1377  js_memcpy(dst, src, size);
   1378 
   1379  if (src->isDependent()) {
   1380    // Special case: if src is a dependent string whose base chain goes through
   1381    // tenured space, then it may point to dead chars -- either because its root
   1382    // base was deduplicated, or its root base's chars were allocated in the
   1383    // nursery. If src's chars pointer will no longer be valid once minor GC is
   1384    // complete, give it its own copy of the chars.
   1385    //
   1386    // Note that the size of any cloned data is *not* included in the "number
   1387    // of bytes tenured" return value here, since the donor owns them and may
   1388    // still be alive and we don't want to double-count.
   1389    JSLinearString::maybeCloneCharsOnPromotion(&dst->asDependent());
   1390    return size;
   1391  }
   1392 
   1393  if (!src->hasOutOfLineChars()) {
   1394    return size;
   1395  }
   1396 
   1397  if (src->ownsMallocedChars()) {
   1398    void* chars = src->asLinear().nonInlineCharsRaw();
   1399    nursery().removeMallocedBufferDuringMinorGC(chars);
   1400    nursery().trackMallocedBufferOnPromotion(
   1401        chars, dst, dst->asLinear().allocSize(), MemoryUse::StringContents);
   1402    return size;
   1403  }
   1404 
   1405  if (src->asLinear().hasStringBuffer()) {
   1406    auto* buffer = src->asLinear().stringBuffer();
   1407    if (dst->isTenured()) {
   1408      // Increment the buffer's refcount because the tenured string now has a
   1409      // reference to it. The nursery's reference will be released at the end of
   1410      // the minor GC in Nursery::sweep.
   1411      buffer->AddRef();
   1412      AddCellMemory(dst, dst->asLinear().allocSize(),
   1413                    MemoryUse::StringContents);
   1414    }
   1415    return size;
   1416  }
   1417 
   1418  // String data is in the nursery and needs to be moved to the malloc heap.
   1419 
   1420  MOZ_ASSERT(nursery().isInside(src->asLinear().nonInlineCharsRaw()));
   1421 
   1422  if (src->hasLatin1Chars()) {
   1423    size += dst->asLinear().maybeMallocCharsOnPromotion<Latin1Char>(&nursery());
   1424  } else {
   1425    size += dst->asLinear().maybeMallocCharsOnPromotion<char16_t>(&nursery());
   1426  }
   1427 
   1428  return size;
   1429 }
   1430 
   1431 size_t js::gc::TenuringTracer::moveBigInt(JS::BigInt* dst, JS::BigInt* src,
   1432                                          AllocKind dstKind) {
   1433  size_t size = Arena::thingSize(dstKind);
   1434 
   1435  MOZ_ASSERT_IF(dst->isTenured(),
   1436                dst->asTenured().getAllocKind() == src->getAllocKind());
   1437 
   1438  // Copy the Cell contents.
   1439  MOZ_ASSERT(OffsetToChunkEnd(src) >= size);
   1440  js_memcpy(dst, src, size);
   1441 
   1442  MOZ_ASSERT(dst->zone() == src->nurseryZone());
   1443 
   1444  if (!src->hasHeapDigits()) {
   1445    return size;
   1446  }
   1447 
   1448  size_t length = dst->digitLength();
   1449  size_t nbytes = length * sizeof(JS::BigInt::Digit);
   1450 
   1451  Nursery::WasBufferMoved result =
   1452      nursery().maybeMoveBufferOnPromotion(&dst->heapDigits_, dst, nbytes);
   1453  if (result == Nursery::BufferMoved) {
   1454    nursery().setDirectForwardingPointer(src->heapDigits_, dst->heapDigits_);
   1455    size += nbytes;
   1456  }
   1457 
   1458  return size;
   1459 }
   1460 
   1461 size_t js::gc::TenuringTracer::moveGetterSetter(GetterSetter* dst,
   1462                                                GetterSetter* src,
   1463                                                AllocKind dstKind) {
   1464  size_t size = Arena::thingSize(dstKind);
   1465 
   1466  MOZ_ASSERT_IF(dst->isTenured(),
   1467                dst->asTenured().getAllocKind() == src->getAllocKind());
   1468 
   1469  // Copy the Cell contents.
   1470  MOZ_ASSERT(OffsetToChunkEnd(src) >= size);
   1471  js_memcpy(dst, src, size);
   1472 
   1473  MOZ_ASSERT(dst->zone() == src->nurseryZone());
   1474 
   1475  return size;
   1476 }
   1477 
   1478 template <typename Key>
   1479 /* static */
   1480 inline HashNumber DeduplicationStringHasher<Key>::hash(const Lookup& lookup) {
   1481  JS::AutoCheckCannotGC nogc;
   1482  HashNumber strHash;
   1483 
   1484  // Include flags in the hash. A string relocation overlay stores either the
   1485  // nursery root base chars or the dependent string nursery base, but does not
   1486  // indicate which one. If strings with different string types were
   1487  // deduplicated, for example, a dependent string gets deduplicated into an
   1488  // extensible string, the base chain would be broken and the root base would
   1489  // be unreachable.
   1490 
   1491  if (lookup->asLinear().hasLatin1Chars()) {
   1492    strHash = mozilla::HashString(lookup->asLinear().latin1Chars(nogc),
   1493                                  lookup->length());
   1494  } else {
   1495    MOZ_ASSERT(lookup->asLinear().hasTwoByteChars());
   1496    strHash = mozilla::HashString(lookup->asLinear().twoByteChars(nogc),
   1497                                  lookup->length());
   1498  }
   1499 
   1500  return mozilla::HashGeneric(strHash, lookup->zone(), lookup->flags());
   1501 }
   1502 
   1503 template <typename Key>
   1504 /* static */
   1505 MOZ_ALWAYS_INLINE bool DeduplicationStringHasher<Key>::match(
   1506    const Key& key, const Lookup& lookup) {
   1507  if (!key->sameLengthAndFlags(*lookup) ||
   1508      key->asTenured().zone() != lookup->zone() ||
   1509      key->asTenured().getAllocKind() != lookup->getAllocKind()) {
   1510    return false;
   1511  }
   1512 
   1513  JS::AutoCheckCannotGC nogc;
   1514 
   1515  if (key->asLinear().hasLatin1Chars()) {
   1516    MOZ_ASSERT(lookup->asLinear().hasLatin1Chars());
   1517    return EqualChars(key->asLinear().latin1Chars(nogc),
   1518                      lookup->asLinear().latin1Chars(nogc), lookup->length());
   1519  }
   1520 
   1521  MOZ_ASSERT(key->asLinear().hasTwoByteChars());
   1522  MOZ_ASSERT(lookup->asLinear().hasTwoByteChars());
   1523  return EqualChars(key->asLinear().twoByteChars(nogc),
   1524                    lookup->asLinear().twoByteChars(nogc), lookup->length());
   1525 }
   1526 
   1527 #ifdef JS_GC_ZEAL
   1528 
   1529 void TenuringTracer::initPromotionReport() {
   1530  MOZ_ASSERT(!promotionStats);
   1531  promotionStats = MakeUnique<PromotionStats>();
   1532  // Ignore OOM.
   1533 }
   1534 
   1535 void TenuringTracer::printPromotionReport(
   1536    JSContext* cx, JS::GCReason reason, const JS::AutoRequireNoGC& nogc) const {
   1537  if (!promotionStats || !promotionStats->shouldPrintReport()) {
   1538    return;
   1539  }
   1540 
   1541  size_t minorGCCount = runtime()->gc.minorGCCount();
   1542  double usedBytes = double(nursery_.previousGC.nurseryUsedBytes);
   1543  double capacityBytes = double(nursery_.previousGC.nurseryCapacity);
   1544  double fractionPromoted = double(getPromotedSize()) / usedBytes;
   1545  double usedMB = usedBytes / (1024 * 1024);
   1546  double capacityMB = capacityBytes / (1024 * 1024);
   1547  fprintf(stderr, "Promotion stats for minor GC %zu:\n", minorGCCount);
   1548  fprintf(stderr, "  Reason: %s\n", ExplainGCReason(reason));
   1549  fprintf(stderr, "  Nursery size: %4.1f MB used of %4.1f MB\n", usedMB,
   1550          capacityMB);
   1551  fprintf(stderr, "  Promotion rate: %5.1f%%\n", fractionPromoted);
   1552 
   1553  promotionStats->printReport(cx, nogc);
   1554 }
   1555 
   1556 void PromotionStats::notePromotedObject(JSObject* obj) {
   1557  objectCount++;
   1558 
   1559  BaseShape* baseShape = obj->shape()->base();
   1560  auto ptr = objectCountByBaseShape.lookupForAdd(baseShape);
   1561  if (!ptr) {
   1562    if (!objectCountByBaseShape.add(ptr, baseShape, 0)) {
   1563      hadOOM = true;
   1564      return;
   1565    }
   1566  }
   1567  ptr->value()++;
   1568 }
   1569 
   1570 void PromotionStats::notePromotedString(JSString* str) {
   1571  stringCount++;
   1572 
   1573  AllocKind kind = str->getAllocKind();
   1574  stringCountByKind[kind]++;
   1575 }
   1576 
   1577 void PromotionStats::notePromotedBigInt(JS::BigInt* bi) { bigIntCount++; }
   1578 
   1579 void PromotionStats::notePromotedGetterSetter(GetterSetter* gs) {
   1580  getterSetterCount++;
   1581 }
   1582 
   1583 bool PromotionStats::shouldPrintReport() const {
   1584  if (hadOOM) {
   1585    return false;
   1586  }
   1587 
   1588  return objectCount || stringCount || bigIntCount || getterSetterCount;
   1589 }
   1590 
   1591 void PromotionStats::printReport(JSContext* cx,
   1592                                 const JS::AutoRequireNoGC& nogc) {
   1593  if (objectCount) {
   1594    fprintf(stderr, "  Objects promoted: %zu\n", objectCount);
   1595    printObjectCounts(cx, nogc);
   1596  }
   1597 
   1598  if (stringCount) {
   1599    fprintf(stderr, "  Strings promoted: %zu\n", stringCount);
   1600    printStringCounts();
   1601  }
   1602 
   1603  if (bigIntCount) {
   1604    fprintf(stderr, "  BigInts promoted: %zu\n", bigIntCount);
   1605  }
   1606 
   1607  if (getterSetterCount) {
   1608    fprintf(stderr, "  GetterSetters promoted: %zu\n", getterSetterCount);
   1609  }
   1610 }
   1611 
   1612 void PromotionStats::printObjectCounts(JSContext* cx,
   1613                                       const JS::AutoRequireNoGC& nogc) {
   1614  CountsVector counts;
   1615 
   1616  for (auto r = objectCountByBaseShape.all(); !r.empty(); r.popFront()) {
   1617    size_t count = r.front().value();
   1618    if (count < AttentionThreshold) {
   1619      continue;
   1620    }
   1621 
   1622    BaseShape* baseShape = r.front().key();
   1623 
   1624    const char* className = baseShape->clasp()->name;
   1625 
   1626    UniqueChars constructorName = getConstructorName(cx, baseShape, nogc);
   1627    const char* constructorChars = constructorName ? constructorName.get() : "";
   1628 
   1629    LabelAndCount entry;
   1630    snprintf(entry.label, sizeof(entry.label), "%s %s", className,
   1631             constructorChars);
   1632    entry.count = count;
   1633    if (!counts.append(entry)) {
   1634      return;
   1635    }
   1636  }
   1637 
   1638  printCounts(counts, objectCount);
   1639 }
   1640 
   1641 UniqueChars PromotionStats::getConstructorName(
   1642    JSContext* cx, BaseShape* baseShape, const JS::AutoRequireNoGC& nogc) {
   1643  TaggedProto taggedProto = baseShape->proto();
   1644  if (taggedProto.isDynamic()) {
   1645    return nullptr;
   1646  }
   1647 
   1648  JSObject* proto = taggedProto.toObjectOrNull();
   1649  if (!proto) {
   1650    return nullptr;
   1651  }
   1652 
   1653  MOZ_ASSERT(!proto->isForwarded());
   1654 
   1655  AutoRealm ar(cx, proto);
   1656  bool found;
   1657  Value value;
   1658  if (!GetOwnPropertyPure(cx, proto, NameToId(cx->names().constructor), &value,
   1659                          &found)) {
   1660    return nullptr;
   1661  }
   1662 
   1663  if (!found || !value.isObject()) {
   1664    return nullptr;
   1665  }
   1666 
   1667  JSFunction* constructor = &value.toObject().as<JSFunction>();
   1668  JSAtom* atom = constructor->maybePartialDisplayAtom();
   1669  if (!atom) {
   1670    return nullptr;
   1671  }
   1672 
   1673  return EncodeAscii(cx, atom);
   1674 }
   1675 
   1676 void PromotionStats::printStringCounts() {
   1677  CountsVector counts;
   1678  for (AllocKind kind : AllAllocKinds()) {
   1679    size_t count = stringCountByKind[kind];
   1680    if (count < AttentionThreshold) {
   1681      continue;
   1682    }
   1683 
   1684    const char* name = AllocKindName(kind);
   1685    LabelAndCount entry;
   1686    strncpy(entry.label, name, sizeof(entry.label));
   1687    entry.label[sizeof(entry.label) - 1] = 0;
   1688    entry.count = count;
   1689    if (!counts.append(entry)) {
   1690      return;
   1691    }
   1692  }
   1693 
   1694  printCounts(counts, stringCount);
   1695 }
   1696 
   1697 void PromotionStats::printCounts(CountsVector& counts, size_t total) {
   1698  std::sort(counts.begin(), counts.end(),
   1699            [](const auto& a, const auto& b) { return a.count > b.count; });
   1700 
   1701  size_t max = std::min(counts.length(), size_t(10));
   1702  for (size_t i = 0; i < max; i++) {
   1703    const auto& entry = counts[i];
   1704    printLine(entry.label, entry.count, total);
   1705  }
   1706 }
   1707 
   1708 void PromotionStats::printLine(const char* name, size_t count, size_t total) {
   1709  double percent = 100.0 * double(count) / double(total);
   1710  fprintf(stderr, "    %5.1f%%: %s\n", percent, name);
   1711 }
   1712 
   1713 #endif
   1714 
   1715 MinorSweepingTracer::MinorSweepingTracer(JSRuntime* rt)
   1716    : GenericTracerImpl(rt, JS::TracerKind::MinorSweeping,
   1717                        JS::WeakMapTraceAction::TraceKeysAndValues) {
   1718  MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime()));
   1719  MOZ_ASSERT(JS::RuntimeHeapIsMinorCollecting());
   1720 }
   1721 
   1722 template <typename T>
   1723 inline void MinorSweepingTracer::onEdge(T** thingp, const char* name) {
   1724  T* thing = *thingp;
   1725  if (thing->isTenured()) {
   1726    MOZ_ASSERT(!IsForwarded(thing));
   1727    return;
   1728  }
   1729 
   1730  MOZ_ASSERT(runtime()->gc.nursery().inCollectedRegion(thing));
   1731  if (IsForwarded(thing)) {
   1732    *thingp = Forwarded(thing);
   1733    return;
   1734  }
   1735 
   1736  *thingp = nullptr;
   1737 }