Pretenuring.cpp (21128B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sw=2 et tw=80: 3 * 4 * This Source Code Form is subject to the terms of the Mozilla Public 5 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 6 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 7 8 #include "gc/Pretenuring.h" 9 10 #include "mozilla/Sprintf.h" 11 12 #include "gc/GCInternals.h" 13 #include "gc/PublicIterators.h" 14 #include "jit/BaselineJIT.h" 15 #include "jit/Invalidation.h" 16 #include "js/Prefs.h" 17 18 #include "gc/Marking-inl.h" 19 #include "gc/PrivateIterators-inl.h" 20 #include "vm/JSScript-inl.h" 21 22 using namespace js; 23 using namespace js::gc; 24 25 // The maximum number of alloc sites to create between each minor 26 // collection. Stop tracking allocation after this limit is reached. This 27 // prevents unbounded time traversing the list during minor GC. 28 static constexpr size_t MaxAllocSitesPerMinorGC = 600; 29 30 // The maximum number of times to invalidate JIT code for a site. After this we 31 // leave the site's state as Unknown and don't pretenure allocations. 32 // Note we use 4 bits to store the invalidation count. 33 static constexpr size_t MaxInvalidationCount = 5; 34 35 // The minimum number of allocated cells needed to determine the survival rate 36 // of cells in newly created arenas. 37 static constexpr size_t MinCellsRequiredForSurvivalRate = 100; 38 39 // The young survival rate below which a major collection is determined to have 40 // a low young survival rate. 41 static constexpr double LowYoungSurvivalThreshold = 0.05; 42 43 // The number of consecutive major collections with a low young survival rate 44 // that must occur before recovery is attempted. 45 static constexpr size_t LowYoungSurvivalCountBeforeRecovery = 2; 46 47 // The proportion of the nursery that must be promoted above which a minor 48 // collection may be determined to have a high nursery survival rate. 49 static constexpr double HighNurserySurvivalPromotionThreshold = 0.6; 50 51 // The number of nursery allocations made by optimized JIT code that must be 52 // promoted above which a minor collection may be determined to have a high 53 // nursery survival rate. 54 static constexpr size_t HighNurserySurvivalOptimizedAllocThreshold = 10000; 55 56 // The number of consecutive minor collections with a high nursery survival rate 57 // that must occur before recovery is attempted. 58 static constexpr size_t HighNurserySurvivalCountBeforeRecovery = 2; 59 60 AllocSite* const AllocSite::EndSentinel = reinterpret_cast<AllocSite*>(1); 61 JSScript* const AllocSite::WasmScript = 62 reinterpret_cast<JSScript*>(AllocSite::STATE_MASK + 1); 63 64 /* static */ 65 void AllocSite::staticAsserts() { 66 static_assert(jit::BaselineMaxScriptLength <= MaxValidPCOffset); 67 } 68 69 bool PretenuringNursery::canCreateAllocSite() { 70 MOZ_ASSERT(allocSitesCreated <= MaxAllocSitesPerMinorGC); 71 return JS::Prefs::site_based_pretenuring() && 72 allocSitesCreated < MaxAllocSitesPerMinorGC; 73 } 74 75 size_t PretenuringNursery::doPretenuring(GCRuntime* gc, JS::GCReason reason, 76 bool validPromotionRate, 77 double promotionRate, 78 const AllocSiteFilter& reportFilter) { 79 size_t sitesActive = 0; 80 size_t sitesPretenured = 0; 81 size_t sitesInvalidated = 0; 82 size_t zonesWithHighNurserySurvival = 0; 83 84 // Zero allocation counts. 85 totalAllocCount_ = 0; 86 for (ZonesIter zone(gc, SkipAtoms); !zone.done(); zone.next()) { 87 for (auto& count : zone->pretenuring.nurseryPromotedCounts) { 88 count = 0; 89 } 90 } 91 92 // Check whether previously optimized code has changed its behaviour and 93 // needs to be recompiled so that it can pretenure its allocations. 94 if (validPromotionRate) { 95 for (ZonesIter zone(gc, SkipAtoms); !zone.done(); zone.next()) { 96 bool highNurserySurvivalRate = 97 promotionRate > HighNurserySurvivalPromotionThreshold && 98 zone->optimizedAllocSite()->nurseryPromotedCount >= 99 HighNurserySurvivalOptimizedAllocThreshold; 100 zone->pretenuring.noteHighNurserySurvivalRate(highNurserySurvivalRate); 101 if (highNurserySurvivalRate) { 102 zonesWithHighNurserySurvival++; 103 } 104 } 105 } 106 107 if (reportFilter.enabled) { 108 AllocSite::printInfoHeader(gc, reason, promotionRate); 109 } 110 111 AllocSite* site = allocatedSites; 112 allocatedSites = AllocSite::EndSentinel; 113 while (site != AllocSite::EndSentinel) { 114 AllocSite* next = site->nextNurseryAllocated; 115 site->nextNurseryAllocated = nullptr; 116 117 if (site->isNormal()) { 118 sitesActive++; 119 updateTotalAllocCounts(site); 120 auto result = 121 site->processSite(gc, NormalSiteAttentionThreshold, reportFilter); 122 if (result == AllocSite::WasPretenured || 123 result == AllocSite::WasPretenuredAndInvalidated) { 124 sitesPretenured++; 125 if (site->hasScript()) { 126 site->script()->realm()->numAllocSitesPretenured++; 127 } 128 } 129 if (result == AllocSite::WasPretenuredAndInvalidated) { 130 sitesInvalidated++; 131 } 132 } else if (site->isMissing()) { 133 sitesActive++; 134 updateTotalAllocCounts(site); 135 site->processMissingSite(reportFilter); 136 } 137 138 site = next; 139 } 140 141 // Catch-all sites don't end up on the list if they are only used from 142 // optimized JIT code, so process them here. 143 144 for (ZonesIter zone(gc, SkipAtoms); !zone.done(); zone.next()) { 145 for (auto& site : zone->pretenuring.unknownAllocSites) { 146 updateTotalAllocCounts(&site); 147 if (site.traceKind() == JS::TraceKind::Object) { 148 site.processCatchAllSite(reportFilter); 149 } else { 150 site.processSite(gc, UnknownSiteAttentionThreshold, reportFilter); 151 } 152 // Result checked in Nursery::doPretenuring. 153 } 154 updateTotalAllocCounts(zone->optimizedAllocSite()); 155 zone->optimizedAllocSite()->processCatchAllSite(reportFilter); 156 157 // The data from the promoted alloc sites is never used so clear them here. 158 for (AllocSite& site : zone->pretenuring.promotedAllocSites) { 159 site.resetNurseryAllocations(); 160 } 161 } 162 163 if (reportFilter.enabled) { 164 AllocSite::printInfoFooter(allocSitesCreated, sitesActive, sitesPretenured, 165 sitesInvalidated); 166 if (zonesWithHighNurserySurvival) { 167 fprintf(stderr, " %zu zones with high nursery survival rate\n", 168 zonesWithHighNurserySurvival); 169 } 170 } 171 172 allocSitesCreated = 0; 173 174 return sitesPretenured; 175 } 176 177 AllocSite::SiteResult AllocSite::processSite( 178 GCRuntime* gc, size_t attentionThreshold, 179 const AllocSiteFilter& reportFilter) { 180 MOZ_ASSERT(isNormal() || isUnknown()); 181 MOZ_ASSERT(nurseryAllocCount >= nurseryPromotedCount); 182 183 SiteResult result = NoChange; 184 185 bool hasPromotionRate = false; 186 double promotionRate = 0.0; 187 bool wasInvalidated = false; 188 189 if (nurseryAllocCount > attentionThreshold) { 190 promotionRate = double(nurseryPromotedCount) / double(nurseryAllocCount); 191 hasPromotionRate = true; 192 193 AllocSite::State prevState = state(); 194 updateStateOnMinorGC(promotionRate); 195 AllocSite::State newState = state(); 196 197 if (prevState == AllocSite::State::Unknown && 198 newState == AllocSite::State::LongLived) { 199 result = WasPretenured; 200 201 // We can optimize JIT code before we realise that a site should be 202 // pretenured. Make sure we invalidate any existing optimized code. 203 if (isNormal() && hasScript()) { 204 wasInvalidated = invalidateScript(gc); 205 if (wasInvalidated) { 206 result = WasPretenuredAndInvalidated; 207 } 208 } 209 } 210 } 211 212 if (reportFilter.matches(*this)) { 213 printInfo(hasPromotionRate, promotionRate, wasInvalidated); 214 } 215 216 resetNurseryAllocations(); 217 218 return result; 219 } 220 221 void AllocSite::processMissingSite(const AllocSiteFilter& reportFilter) { 222 MOZ_ASSERT(isMissing()); 223 MOZ_ASSERT(nurseryAllocCount >= nurseryPromotedCount); 224 225 // Forward counts from missing sites to the relevant unknown site. 226 AllocSite* unknownSite = zone()->unknownAllocSite(traceKind()); 227 unknownSite->nurseryAllocCount += nurseryAllocCount; 228 unknownSite->nurseryPromotedCount += nurseryPromotedCount; 229 230 // Update state but only so we can report it. 231 bool hasPromotionRate = false; 232 double promotionRate = 0.0; 233 if (nurseryAllocCount > NormalSiteAttentionThreshold) { 234 promotionRate = double(nurseryPromotedCount) / double(nurseryAllocCount); 235 hasPromotionRate = true; 236 updateStateOnMinorGC(promotionRate); 237 } 238 239 if (reportFilter.matches(*this)) { 240 printInfo(hasPromotionRate, promotionRate, false); 241 } 242 243 resetNurseryAllocations(); 244 } 245 246 void AllocSite::processCatchAllSite(const AllocSiteFilter& reportFilter) { 247 MOZ_ASSERT(isUnknown() || isOptimized()); 248 249 if (!hasNurseryAllocations()) { 250 return; 251 } 252 253 if (reportFilter.matches(*this)) { 254 printInfo(false, 0.0, false); 255 } 256 257 resetNurseryAllocations(); 258 } 259 260 void PretenuringNursery::updateTotalAllocCounts(AllocSite* site) { 261 JS::TraceKind kind = site->traceKind(); 262 totalAllocCount_ += site->nurseryAllocCount; 263 PretenuringZone& zone = site->zone()->pretenuring; 264 size_t i = size_t(kind); 265 MOZ_ASSERT(i < std::size(zone.nurseryPromotedCounts)); 266 zone.nurseryPromotedCounts[i] += site->nurseryPromotedCount; 267 } 268 269 bool AllocSite::invalidateScript(GCRuntime* gc) { 270 CancelOffThreadIonCompile(script()); 271 272 if (!script()->hasIonScript()) { 273 return false; 274 } 275 276 if (invalidationLimitReached()) { 277 MOZ_ASSERT(state() == State::Unknown); 278 return false; 279 } 280 281 invalidationCount++; 282 if (invalidationLimitReached()) { 283 setState(State::Unknown); 284 } 285 286 JSContext* cx = gc->rt->mainContextFromOwnThread(); 287 jit::Invalidate(cx, script(), 288 /* resetUses = */ false, 289 /* cancelOffThread = */ true); 290 return true; 291 } 292 293 bool AllocSite::invalidationLimitReached() const { 294 MOZ_ASSERT(invalidationCount <= MaxInvalidationCount); 295 return invalidationCount == MaxInvalidationCount; 296 } 297 298 void PretenuringNursery::maybeStopPretenuring(GCRuntime* gc) { 299 for (GCZonesIter zone(gc); !zone.done(); zone.next()) { 300 double rate; 301 if (zone->pretenuring.calculateYoungTenuredSurvivalRate(&rate)) { 302 bool lowYoungSurvivalRate = rate < LowYoungSurvivalThreshold; 303 zone->pretenuring.noteLowYoungTenuredSurvivalRate(lowYoungSurvivalRate); 304 } 305 } 306 } 307 308 void AllocSite::updateStateOnMinorGC(double promotionRate) { 309 // The state changes based on whether the promotion rate is deemed high 310 // (greater that 90%): 311 // 312 // high high 313 // ------------------> ------------------> 314 // ShortLived Unknown LongLived 315 // <------------------ <------------------ 316 // !high !high 317 // 318 // The nursery is used to allocate if the site's state is Unknown or 319 // ShortLived. There are no direct transition between ShortLived and LongLived 320 // to avoid pretenuring sites that we've recently observed being short-lived. 321 322 if (invalidationLimitReached()) { 323 MOZ_ASSERT(state() == State::Unknown); 324 return; 325 } 326 327 bool highPromotionRate = promotionRate >= 0.9; 328 329 switch (state()) { 330 case State::Unknown: 331 if (highPromotionRate) { 332 setState(State::LongLived); 333 } else { 334 setState(State::ShortLived); 335 } 336 break; 337 338 case State::ShortLived: { 339 if (highPromotionRate) { 340 setState(State::Unknown); 341 } 342 break; 343 } 344 345 case State::LongLived: { 346 if (!highPromotionRate) { 347 setState(State::Unknown); 348 } 349 break; 350 } 351 } 352 } 353 354 bool AllocSite::maybeResetState() { 355 if (invalidationLimitReached()) { 356 MOZ_ASSERT(state() == State::Unknown); 357 return false; 358 } 359 360 invalidationCount++; 361 setState(State::Unknown); 362 return true; 363 } 364 365 void AllocSite::trace(JSTracer* trc) { 366 if (hasScript()) { 367 JSScript* s = script(); 368 TraceManuallyBarrieredEdge(trc, &s, "AllocSite script"); 369 if (s != script()) { 370 setScript(s); 371 } 372 } 373 } 374 375 bool AllocSite::traceWeak(JSTracer* trc) { 376 if (hasScript()) { 377 JSScript* s = script(); 378 if (!TraceManuallyBarrieredWeakEdge(trc, &s, "AllocSite script")) { 379 return false; 380 } 381 if (s != script()) { 382 setScript(s); 383 } 384 } 385 386 return true; 387 } 388 389 bool AllocSite::needsSweep(JSTracer* trc) const { 390 if (hasScript()) { 391 JSScript* s = script(); 392 return IsAboutToBeFinalizedUnbarriered(s); 393 } 394 395 return false; 396 } 397 398 bool PretenuringZone::calculateYoungTenuredSurvivalRate(double* rateOut) { 399 MOZ_ASSERT(allocCountInNewlyCreatedArenas >= 400 survivorCountInNewlyCreatedArenas); 401 if (allocCountInNewlyCreatedArenas < MinCellsRequiredForSurvivalRate) { 402 return false; 403 } 404 405 *rateOut = double(survivorCountInNewlyCreatedArenas) / 406 double(allocCountInNewlyCreatedArenas); 407 return true; 408 } 409 410 void PretenuringZone::noteLowYoungTenuredSurvivalRate( 411 bool lowYoungSurvivalRate) { 412 if (lowYoungSurvivalRate) { 413 lowYoungTenuredSurvivalCount++; 414 } else { 415 lowYoungTenuredSurvivalCount = 0; 416 } 417 } 418 419 void PretenuringZone::noteHighNurserySurvivalRate( 420 bool highNurserySurvivalRate) { 421 if (highNurserySurvivalRate) { 422 highNurserySurvivalCount++; 423 } else { 424 highNurserySurvivalCount = 0; 425 } 426 } 427 428 bool PretenuringZone::shouldResetNurseryAllocSites() { 429 bool shouldReset = 430 highNurserySurvivalCount >= HighNurserySurvivalCountBeforeRecovery; 431 if (shouldReset) { 432 highNurserySurvivalCount = 0; 433 } 434 return shouldReset; 435 } 436 437 bool PretenuringZone::shouldResetPretenuredAllocSites() { 438 bool shouldReset = 439 lowYoungTenuredSurvivalCount >= LowYoungSurvivalCountBeforeRecovery; 440 if (shouldReset) { 441 lowYoungTenuredSurvivalCount = 0; 442 } 443 return shouldReset; 444 } 445 446 static const char* AllocSiteKindName(AllocSite::Kind kind) { 447 switch (kind) { 448 case AllocSite::Kind::Normal: 449 return "normal"; 450 case AllocSite::Kind::Unknown: 451 return "unknown"; 452 case AllocSite::Kind::Optimized: 453 return "optimized"; 454 case AllocSite::Kind::Missing: 455 return "missing"; 456 default: 457 MOZ_CRASH("Bad AllocSite kind"); 458 } 459 } 460 461 /* static */ 462 void AllocSite::printInfoHeader(GCRuntime* gc, JS::GCReason reason, 463 double promotionRate) { 464 fprintf(stderr, 465 "Pretenuring info after minor GC %zu for %s reason with promotion " 466 "rate %4.1f%%:\n", 467 size_t(gc->minorGCCount()), ExplainGCReason(reason), 468 promotionRate * 100.0); 469 fprintf(stderr, " %-16s %-16s %-20s %-12s %-9s %-9s %-8s %-8s %-6s %-10s\n", 470 "Site", "Zone", "Location", "BytecodeOp", "SiteKind", "TraceKind", 471 "NAllocs", "Promotes", "PRate", "State"); 472 } 473 474 static const char* FindBaseName(const char* filename) { 475 #ifdef XP_WIN 476 constexpr char PathSeparator = '\\'; 477 #else 478 constexpr char PathSeparator = '/'; 479 #endif 480 481 const char* lastSep = strrchr(filename, PathSeparator); 482 if (!lastSep) { 483 return filename; 484 } 485 486 return lastSep + 1; 487 } 488 489 void AllocSite::printInfo(bool hasPromotionRate, double promotionRate, 490 bool wasInvalidated) const { 491 // Zone. 492 fprintf(stderr, " %16p %16p", this, zone()); 493 494 // Location and bytecode op (not present for catch-all sites). 495 char location[21] = {'\0'}; 496 char opName[13] = {'\0'}; 497 if (hasScript()) { 498 uint32_t line = PCToLineNumber(script(), script()->offsetToPC(pcOffset())); 499 const char* scriptName = FindBaseName(script()->filename()); 500 SprintfLiteral(location, "%s:%u", scriptName, line); 501 BytecodeLocation location = script()->offsetToLocation(pcOffset()); 502 SprintfLiteral(opName, "%s", CodeName(location.getOp())); 503 } 504 fprintf(stderr, " %-20s %-12s", location, opName); 505 506 // Which kind of site this is. 507 fprintf(stderr, " %-9s", AllocSiteKindName(kind())); 508 509 // Trace kind, except for optimized sites. 510 const char* traceKindName = ""; 511 if (!isOptimized()) { 512 traceKindName = JS::GCTraceKindToAscii(traceKind()); 513 } 514 fprintf(stderr, " %-9s", traceKindName); 515 516 // Nursery allocation count, missing for optimized sites. 517 char buffer[16] = {'\0'}; 518 if (!isOptimized()) { 519 SprintfLiteral(buffer, "%8" PRIu32, nurseryAllocCount); 520 } 521 fprintf(stderr, " %8s", buffer); 522 523 // Nursery promotion count. 524 fprintf(stderr, " %8" PRIu32, nurseryPromotedCount); 525 526 // Promotion rate, if there were enough allocations. 527 buffer[0] = '\0'; 528 if (hasPromotionRate) { 529 SprintfLiteral(buffer, "%5.1f%%", std::min(1.0, promotionRate) * 100); 530 } 531 fprintf(stderr, " %6s", buffer); 532 533 // Current state where applicable. 534 const char* state = ""; 535 if (!isOptimized()) { 536 state = stateName(); 537 } 538 fprintf(stderr, " %-10s", state); 539 540 // Whether the associated script was invalidated. 541 if (wasInvalidated) { 542 fprintf(stderr, " invalidated"); 543 } 544 545 fprintf(stderr, "\n"); 546 } 547 548 /* static */ 549 void AllocSite::printInfoFooter(size_t sitesCreated, size_t sitesActive, 550 size_t sitesPretenured, 551 size_t sitesInvalidated) { 552 fprintf(stderr, 553 " %zu alloc sites created, %zu active, %zu pretenured, %zu " 554 "invalidated\n", 555 sitesCreated, sitesActive, sitesPretenured, sitesInvalidated); 556 } 557 558 const char* AllocSite::stateName() const { 559 switch (state()) { 560 case State::ShortLived: 561 return "ShortLived"; 562 case State::Unknown: 563 return "Unknown"; 564 case State::LongLived: 565 return "LongLived"; 566 } 567 568 MOZ_CRASH("Unknown state"); 569 } 570 571 static bool StringIsPrefix(const CharRange& prefix, const char* whole) { 572 MOZ_ASSERT(prefix.length() != 0); 573 return strncmp(prefix.begin().get(), whole, prefix.length()) == 0; 574 } 575 576 /* static */ 577 bool AllocSiteFilter::readFromString(const char* string, 578 AllocSiteFilter* filter) { 579 *filter = AllocSiteFilter(); 580 581 CharRangeVector parts; 582 if (!SplitStringBy(string, ',', &parts)) { 583 MOZ_CRASH("OOM parsing AllocSiteFilter"); 584 } 585 586 for (const auto& part : parts) { 587 if (StringIsPrefix(part, "normal")) { 588 filter->siteKindMask |= 1 << size_t(AllocSite::Kind::Normal); 589 } else if (StringIsPrefix(part, "unknown")) { 590 filter->siteKindMask |= 1 << size_t(AllocSite::Kind::Unknown); 591 } else if (StringIsPrefix(part, "optimized")) { 592 filter->siteKindMask |= 1 << size_t(AllocSite::Kind::Optimized); 593 } else if (StringIsPrefix(part, "missing")) { 594 filter->siteKindMask |= 1 << size_t(AllocSite::Kind::Missing); 595 } else if (StringIsPrefix(part, "object")) { 596 filter->traceKindMask |= 1 << size_t(JS::TraceKind::Object); 597 } else if (StringIsPrefix(part, "string")) { 598 filter->traceKindMask |= 1 << size_t(JS::TraceKind::String); 599 } else if (StringIsPrefix(part, "bigint")) { 600 filter->traceKindMask |= 1 << size_t(JS::TraceKind::BigInt); 601 } else if (StringIsPrefix(part, "longlived")) { 602 filter->stateMask |= 1 << size_t(AllocSite::State::LongLived); 603 } else if (StringIsPrefix(part, "shortlived")) { 604 filter->stateMask |= 1 << size_t(AllocSite::State::ShortLived); 605 } else { 606 char* end; 607 filter->allocThreshold = strtol(part.begin().get(), &end, 10); 608 if (end < part.end().get()) { 609 return false; 610 } 611 } 612 } 613 614 filter->enabled = true; 615 616 return true; 617 } 618 619 template <typename Enum> 620 static bool MaskFilterMatches(uint8_t mask, Enum value) { 621 static_assert(std::is_enum_v<Enum>); 622 623 if (mask == 0) { 624 return true; // Match if filter not specified. 625 } 626 627 MOZ_ASSERT(size_t(value) < 8); 628 uint8_t bit = 1 << size_t(value); 629 return (mask & bit) != 0; 630 } 631 632 bool AllocSiteFilter::matches(const AllocSite& site) const { 633 // The state is not relevant for other kinds so skip filter. 634 bool matchState = site.isNormal() || site.isMissing(); 635 636 return enabled && 637 (allocThreshold == 0 || site.allocCount() >= allocThreshold) && 638 MaskFilterMatches(siteKindMask, site.kind()) && 639 MaskFilterMatches(traceKindMask, site.traceKind()) && 640 (!matchState || MaskFilterMatches(stateMask, site.state())); 641 } 642 643 #ifdef JS_GC_ZEAL 644 645 AllocSite* js::gc::GetOrCreateMissingAllocSite(JSContext* cx, JSScript* script, 646 uint32_t pcOffset, 647 JS::TraceKind traceKind) { 648 // Doesn't increment allocSitesCreated so as not to disturb pretenuring. 649 650 Zone* zone = cx->zone(); 651 auto& missingSites = zone->missingSites; 652 if (!missingSites) { 653 missingSites = MakeUnique<MissingAllocSites>(zone); 654 if (!missingSites) { 655 return nullptr; 656 } 657 } 658 659 auto scriptPtr = missingSites->scriptMap.lookupForAdd(script); 660 if (!scriptPtr && !missingSites->scriptMap.add( 661 scriptPtr, script, MissingAllocSites::SiteMap())) { 662 return nullptr; 663 } 664 auto& siteMap = scriptPtr->value(); 665 666 auto sitePtr = siteMap.lookupForAdd(pcOffset); 667 if (!sitePtr) { 668 UniquePtr<AllocSite> site = MakeUnique<AllocSite>( 669 zone, script, pcOffset, traceKind, AllocSite::Kind::Missing); 670 if (!site || !siteMap.add(sitePtr, pcOffset, std::move(site))) { 671 return nullptr; 672 } 673 } 674 675 return sitePtr->value().get(); 676 } 677 678 #endif // JS_GC_ZEAL