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 }