tor-browser

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

Pretenuring.h (15751B)


      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 * Pretenuring.
      9 *
     10 * Some kinds of GC cells can be allocated in either the nursery or the tenured
     11 * heap. The pretenuring system decides where to allocate such cells based on
     12 * their expected lifetime with the aim of minimising total collection time.
     13 *
     14 * Lifetime is predicted based on data gathered about the cells' allocation
     15 * site. This data is gathered in the middle JIT tiers, after code has stopped
     16 * executing in the interpreter and before we generate fully optimized code.
     17 */
     18 
     19 #ifndef gc_Pretenuring_h
     20 #define gc_Pretenuring_h
     21 
     22 #include <algorithm>
     23 
     24 #include "gc/AllocKind.h"
     25 #include "js/TypeDecls.h"
     26 
     27 class JS_PUBLIC_API JSTracer;
     28 
     29 namespace JS {
     30 enum class GCReason;
     31 }  // namespace JS
     32 
     33 namespace js::gc {
     34 
     35 struct AllocSiteFilter;
     36 class GCRuntime;
     37 class PretenuringNursery;
     38 
     39 // Number of trace kinds supportd by the nursery. These are arranged at the
     40 // start of JS::TraceKind.
     41 static constexpr size_t NurseryTraceKinds = 4;
     42 
     43 // The number of nursery allocations at which to pay attention to an allocation
     44 // site. This must be large enough to ensure we have enough information to infer
     45 // the lifetime and also large enough to avoid pretenuring low volume allocation
     46 // sites.
     47 static constexpr size_t NormalSiteAttentionThreshold = 200;
     48 static constexpr size_t UnknownSiteAttentionThreshold = 30000;
     49 
     50 enum class CatchAllAllocSite { Unknown, Optimized };
     51 
     52 // Information about an allocation site.
     53 //
     54 // Nursery cells contain a pointer to one of these in their cell header (stored
     55 // before the cell). The site can relate to either a specific JS bytecode
     56 // instruction, a specific WebAssembly type, or can be a catch-all instance for
     57 // unknown sites or JS JIT optimized code.
     58 class AllocSite {
     59 public:
     60  enum class Kind : uint32_t {
     61    Normal = 0,
     62    Unknown = 1,
     63    Optimized = 2,
     64    Missing = 3,
     65    Tenuring = 4,
     66  };
     67  enum class State : uint32_t { Unknown = 0, LongLived = 1, ShortLived = 2 };
     68 
     69  // We can convert between state and heap by extracting a single bit.
     70  static constexpr int32_t LONG_LIVED_BIT = 1;
     71  static_assert((uint32_t(State::Unknown) & LONG_LIVED_BIT) ==
     72                uint32_t(Heap::Default));
     73  static_assert((uint32_t(State::LongLived) & LONG_LIVED_BIT) ==
     74                uint32_t(Heap::Tenured));
     75  static_assert((uint32_t(State::ShortLived) & LONG_LIVED_BIT) ==
     76                uint32_t(Heap::Default));
     77 
     78 private:
     79  JS::Zone* zone_ = nullptr;
     80 
     81  // Word storing JSScript pointer and site state.
     82  //
     83  // The script pointer is the script that owns this allocation site, a special
     84  // sentinel script for wasm sites, or null for unknown sites. This is used
     85  // when we need to invalidate the script.
     86  uintptr_t scriptAndState = uintptr_t(State::Unknown);
     87  static constexpr uintptr_t STATE_MASK = BitMask(2);
     88 
     89  // Next pointer forming a linked list of sites which will have reached the
     90  // allocation threshold and will be processed at the end of the next nursery
     91  // collection.
     92  AllocSite* nextNurseryAllocated = nullptr;
     93 
     94  // Bytecode offset of this allocation site. Only valid if hasScript().
     95  // Note that the offset does not need to correspond with the script stored in
     96  // this AllocSite, because if we're doing trial-inlining, the script will be
     97  // the outer script and the pc offset can be in an inlined script.
     98  uint32_t pcOffset_ : 29;
     99  static constexpr uint32_t InvalidPCOffset = Bit(29) - 1;
    100 
    101  uint32_t kind_ : 3;
    102 
    103  // Number of nursery allocations at this site since it was last processed by
    104  // processSite().
    105  uint32_t nurseryAllocCount = 0;
    106 
    107  // Number of nursery allocations at this site that were promoted since it was
    108  // last processed by processSite().
    109  uint32_t nurseryPromotedCount : 24;
    110 
    111  // Number of times the script has been invalidated.
    112  uint32_t invalidationCount : 4;
    113 
    114  // The trace kind of the allocation. Only kinds up to NurseryTraceKinds are
    115  // allowed.
    116  uint32_t traceKind_ : 4;
    117 
    118  static AllocSite* const EndSentinel;
    119 
    120  // Sentinel script for wasm sites.
    121  static JSScript* const WasmScript;
    122 
    123  friend class PretenuringZone;
    124  friend class PretenuringNursery;
    125 
    126  uintptr_t rawScript() const { return scriptAndState & ~STATE_MASK; }
    127 
    128 public:
    129  static constexpr uint32_t EnvSitePCOffset = InvalidPCOffset - 1;
    130  static constexpr uint32_t MaxValidPCOffset = EnvSitePCOffset - 1;
    131 
    132  // Default constructor. Clients must call one of the init methods afterwards.
    133  AllocSite()
    134      : pcOffset_(InvalidPCOffset),
    135        kind_(uint32_t(Kind::Unknown)),
    136        nurseryPromotedCount(0),
    137        invalidationCount(0),
    138        traceKind_(0) {}
    139 
    140  // Create a site for an opcode in the given script.
    141  AllocSite(JS::Zone* zone, JSScript* script, uint32_t pcOffset,
    142            JS::TraceKind traceKind, Kind siteKind = Kind::Normal)
    143      : zone_(zone),
    144        pcOffset_(pcOffset),
    145        kind_(uint32_t(siteKind)),
    146        nurseryPromotedCount(0),
    147        invalidationCount(0),
    148        traceKind_(uint32_t(traceKind)) {
    149    MOZ_ASSERT(pcOffset <= MaxValidPCOffset || pcOffset == EnvSitePCOffset);
    150    MOZ_ASSERT(pcOffset_ == pcOffset);
    151    setScript(script);
    152  }
    153 
    154  ~AllocSite() {
    155    MOZ_ASSERT(!isInAllocatedList());
    156    MOZ_ASSERT(nurseryAllocCount < NormalSiteAttentionThreshold);
    157    MOZ_ASSERT(nurseryPromotedCount < NormalSiteAttentionThreshold);
    158  }
    159 
    160  void initUnknownSite(JS::Zone* zone, JS::TraceKind traceKind) {
    161    assertUninitialized();
    162    zone_ = zone;
    163    traceKind_ = uint32_t(traceKind);
    164    MOZ_ASSERT(traceKind_ < NurseryTraceKinds);
    165  }
    166 
    167  void initOptimizedSite(JS::Zone* zone) {
    168    assertUninitialized();
    169    zone_ = zone;
    170    kind_ = uint32_t(Kind::Optimized);
    171  }
    172 
    173  void initTenuringSite(JS::Zone* zone) {
    174    assertUninitialized();
    175    zone_ = zone;
    176    scriptAndState = uintptr_t(State::LongLived);
    177    kind_ = uint32_t(Kind::Tenuring);
    178  }
    179 
    180  // Initialize a site to be a wasm site.
    181  void initWasm(JS::Zone* zone) {
    182    assertUninitialized();
    183    zone_ = zone;
    184    kind_ = uint32_t(Kind::Normal);
    185    setScript(WasmScript);
    186    traceKind_ = uint32_t(JS::TraceKind::Object);
    187  }
    188 
    189  void assertUninitialized() {
    190 #ifdef DEBUG
    191    MOZ_ASSERT(!zone_);
    192    MOZ_ASSERT(isUnknown());
    193    MOZ_ASSERT(scriptAndState == uintptr_t(State::Unknown));
    194    MOZ_ASSERT(nurseryPromotedCount == 0);
    195    MOZ_ASSERT(invalidationCount == 0);
    196 #endif
    197  }
    198 
    199  static void staticAsserts();
    200 
    201  JS::Zone* zone() const { return zone_; }
    202 
    203  JS::TraceKind traceKind() const { return JS::TraceKind(traceKind_); }
    204 
    205  State state() const { return State(scriptAndState & STATE_MASK); }
    206 
    207  // Whether this site has a script associated with it. This is not true if
    208  // this site is for a wasm site.
    209  bool hasScript() const {
    210    return rawScript() && rawScript() != uintptr_t(WasmScript);
    211  }
    212  JSScript* script() const {
    213    MOZ_ASSERT(hasScript());
    214    return reinterpret_cast<JSScript*>(rawScript());
    215  }
    216 
    217  uint32_t pcOffset() const {
    218    MOZ_ASSERT(hasScript());
    219    MOZ_ASSERT(pcOffset_ != InvalidPCOffset);
    220    return pcOffset_;
    221  }
    222 
    223  bool isNormal() const { return kind() == Kind::Normal; }
    224  bool isUnknown() const { return kind() == Kind::Unknown; }
    225  bool isOptimized() const { return kind() == Kind::Optimized; }
    226  bool isMissing() const { return kind() == Kind::Missing; }
    227  bool isTenuring() const { return kind() == Kind::Tenuring; }
    228 
    229  Kind kind() const {
    230    MOZ_ASSERT((Kind(kind_) == Kind::Normal || Kind(kind_) == Kind::Missing) ==
    231               (rawScript() != 0));
    232    return Kind(kind_);
    233  }
    234 
    235  bool isInAllocatedList() const { return nextNurseryAllocated; }
    236 
    237  // Whether allocations at this site should be allocated in the nursery or the
    238  // tenured heap.
    239  Heap initialHeap() const {
    240    Heap heap = Heap(uint32_t(state()) & LONG_LIVED_BIT);
    241    MOZ_ASSERT_IF(isTenuring(), heap == Heap::Tenured);
    242    MOZ_ASSERT_IF(!isTenuring() && !isNormal(), heap == Heap::Default);
    243    return heap;
    244  }
    245 
    246  bool hasNurseryAllocations() const {
    247    return nurseryAllocCount != 0 || nurseryPromotedCount != 0;
    248  }
    249  void resetNurseryAllocations() {
    250    nurseryAllocCount = 0;
    251    nurseryPromotedCount = 0;
    252  }
    253 
    254  uint32_t incAllocCount() { return ++nurseryAllocCount; }
    255  uint32_t* nurseryAllocCountAddress() { return &nurseryAllocCount; }
    256 
    257  void incPromotedCount() {
    258    // The nursery is not large enough for this to overflow.
    259    nurseryPromotedCount++;
    260    MOZ_ASSERT(nurseryPromotedCount != 0);
    261  }
    262 
    263  size_t allocCount() const {
    264    return std::max(nurseryAllocCount, nurseryPromotedCount);
    265  }
    266 
    267  // Called for every active alloc site after minor GC.
    268  enum SiteResult { NoChange, WasPretenured, WasPretenuredAndInvalidated };
    269  SiteResult processSite(GCRuntime* gc, size_t attentionThreshold,
    270                         const AllocSiteFilter& reportFilter);
    271  void processMissingSite(const AllocSiteFilter& reportFilter);
    272  void processCatchAllSite(const AllocSiteFilter& reportFilter);
    273 
    274  void updateStateOnMinorGC(double promotionRate);
    275 
    276  // Reset the state to 'Unknown' unless we have reached the invalidation limit
    277  // for this site. Return whether the state was reset.
    278  bool maybeResetState();
    279 
    280  bool invalidationLimitReached() const;
    281  bool invalidateScript(GCRuntime* gc);
    282 
    283  void trace(JSTracer* trc);
    284  bool traceWeak(JSTracer* trc);
    285  bool needsSweep(JSTracer* trc) const;
    286 
    287  static void printInfoHeader(GCRuntime* gc, JS::GCReason reason,
    288                              double promotionRate);
    289  static void printInfoFooter(size_t sitesCreated, size_t sitesActive,
    290                              size_t sitesPretenured, size_t sitesInvalidated);
    291  void printInfo(bool hasPromotionRate, double promotionRate,
    292                 bool wasInvalidated) const;
    293 
    294  static constexpr size_t offsetOfScriptAndState() {
    295    return offsetof(AllocSite, scriptAndState);
    296  }
    297  static constexpr size_t offsetOfNurseryAllocCount() {
    298    return offsetof(AllocSite, nurseryAllocCount);
    299  }
    300  static constexpr size_t offsetOfNextNurseryAllocated() {
    301    return offsetof(AllocSite, nextNurseryAllocated);
    302  }
    303 
    304 private:
    305  void setScript(JSScript* newScript) {
    306    MOZ_ASSERT((uintptr_t(newScript) & STATE_MASK) == 0);
    307    scriptAndState = uintptr_t(newScript) | uintptr_t(state());
    308  }
    309 
    310  void setState(State newState) {
    311    MOZ_ASSERT((uintptr_t(newState) & ~STATE_MASK) == 0);
    312    scriptAndState = rawScript() | uintptr_t(newState);
    313  }
    314 
    315  const char* stateName() const;
    316 };
    317 
    318 // Pretenuring information stored per zone.
    319 class PretenuringZone {
    320 public:
    321  // Catch-all allocation site instance used when the actual site is unknown, or
    322  // when optimized JIT code allocates a GC thing that's not handled by the
    323  // pretenuring system.
    324  AllocSite unknownAllocSites[NurseryTraceKinds];
    325 
    326  // Catch-all allocation instance used by optimized JIT code when allocating GC
    327  // things that are handled by the pretenuring system.  Allocation counts are
    328  // not recorded by optimized JIT code.
    329  AllocSite optimizedAllocSite;
    330 
    331  // Alloc Site which always tenure allocates. Used to avoid type punning
    332  // when JIT-compiling for the Realm local allocation site.
    333  AllocSite tenuringAllocSite;
    334 
    335  // Allocation sites used for nursery cells promoted to the next nursery
    336  // generation that didn't come from optimized alloc sites.
    337  AllocSite promotedAllocSites[NurseryTraceKinds];
    338 
    339  // Count of tenured cell allocations made between each major collection and
    340  // how many survived.
    341  uint32_t allocCountInNewlyCreatedArenas = 0;
    342  uint32_t survivorCountInNewlyCreatedArenas = 0;
    343 
    344  // Count of successive collections that had a low young tenured survival
    345  // rate. Used to discard optimized code if we get the pretenuring decision
    346  // wrong.
    347  uint32_t lowYoungTenuredSurvivalCount = 0;
    348 
    349  // Count of successive nursery collections that had a high survival rate for
    350  // objects allocated by optimized code. Used to discard optimized code if we
    351  // get the pretenuring decision wrong.
    352  uint32_t highNurserySurvivalCount = 0;
    353 
    354  // Total promotion count by trace kind. Calculated during nursery collection.
    355  uint32_t nurseryPromotedCounts[NurseryTraceKinds] = {0};
    356 
    357  explicit PretenuringZone(JS::Zone* zone) {
    358    for (uint32_t i = 0; i < NurseryTraceKinds; i++) {
    359      unknownAllocSites[i].initUnknownSite(zone, JS::TraceKind(i));
    360      promotedAllocSites[i].initUnknownSite(zone, JS::TraceKind(i));
    361    }
    362    optimizedAllocSite.initOptimizedSite(zone);
    363    tenuringAllocSite.initTenuringSite(zone);
    364  }
    365 
    366  AllocSite& unknownAllocSite(JS::TraceKind kind) {
    367    size_t i = size_t(kind);
    368    MOZ_ASSERT(i < NurseryTraceKinds);
    369    return unknownAllocSites[i];
    370  }
    371 
    372  AllocSite& promotedAllocSite(JS::TraceKind kind) {
    373    size_t i = size_t(kind);
    374    MOZ_ASSERT(i < NurseryTraceKinds);
    375    return promotedAllocSites[i];
    376  }
    377 
    378  void clearCellCountsInNewlyCreatedArenas() {
    379    allocCountInNewlyCreatedArenas = 0;
    380    survivorCountInNewlyCreatedArenas = 0;
    381  }
    382  void updateCellCountsInNewlyCreatedArenas(uint32_t allocCount,
    383                                            uint32_t survivorCount) {
    384    allocCountInNewlyCreatedArenas += allocCount;
    385    survivorCountInNewlyCreatedArenas += survivorCount;
    386  }
    387 
    388  bool calculateYoungTenuredSurvivalRate(double* rateOut);
    389 
    390  void noteLowYoungTenuredSurvivalRate(bool lowYoungSurvivalRate);
    391  void noteHighNurserySurvivalRate(bool highNurserySurvivalRate);
    392 
    393  // Recovery: if code behaviour change we may need to reset allocation site
    394  // state and invalidate JIT code.
    395  bool shouldResetNurseryAllocSites();
    396  bool shouldResetPretenuredAllocSites();
    397 
    398  uint32_t nurseryPromotedCount(JS::TraceKind kind) const {
    399    size_t i = size_t(kind);
    400    MOZ_ASSERT(i < std::size(nurseryPromotedCounts));
    401    return nurseryPromotedCounts[i];
    402  }
    403 };
    404 
    405 // Pretenuring information stored as part of the the GC nursery.
    406 class PretenuringNursery {
    407  AllocSite* allocatedSites;
    408 
    409  size_t allocSitesCreated = 0;
    410 
    411  uint32_t totalAllocCount_ = 0;
    412 
    413 public:
    414  PretenuringNursery() : allocatedSites(AllocSite::EndSentinel) {}
    415 
    416  bool hasAllocatedSites() const {
    417    return allocatedSites != AllocSite::EndSentinel;
    418  }
    419 
    420  bool canCreateAllocSite();
    421  void noteAllocSiteCreated() { allocSitesCreated++; }
    422 
    423  void insertIntoAllocatedList(AllocSite* site) {
    424    MOZ_ASSERT(!site->isInAllocatedList());
    425    site->nextNurseryAllocated = allocatedSites;
    426    allocatedSites = site;
    427  }
    428 
    429  size_t doPretenuring(GCRuntime* gc, JS::GCReason reason,
    430                       bool validPromotionRate, double promotionRate,
    431                       const AllocSiteFilter& reportFilter);
    432 
    433  void maybeStopPretenuring(GCRuntime* gc);
    434 
    435  uint32_t totalAllocCount() const { return totalAllocCount_; }
    436 
    437  void* addressOfAllocatedSites() { return &allocatedSites; }
    438 
    439 private:
    440  void updateTotalAllocCounts(AllocSite* site);
    441 };
    442 
    443 // Describes which alloc sites to report on, if any.
    444 struct AllocSiteFilter {
    445  size_t allocThreshold = 0;
    446  uint8_t siteKindMask = 0;
    447  uint8_t traceKindMask = 0;
    448  uint8_t stateMask = 0;
    449  bool enabled = false;
    450 
    451  bool matches(const AllocSite& site) const;
    452 
    453  static bool readFromString(const char* string, AllocSiteFilter* filter);
    454 };
    455 
    456 #ifdef JS_GC_ZEAL
    457 
    458 // To help discover good places to add allocation sites, automatically create an
    459 // allocation site for an allocation that didn't supply one.
    460 AllocSite* GetOrCreateMissingAllocSite(JSContext* cx, JSScript* script,
    461                                       uint32_t pcOffset,
    462                                       JS::TraceKind traceKind);
    463 
    464 #endif  // JS_GC_ZEAL
    465 
    466 }  // namespace js::gc
    467 
    468 #endif /* gc_Pretenuring_h */