tor-browser

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

ZoneAllocator.h (11437B)


      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 * Public header for allocating memory associated with GC things.
      9 */
     10 
     11 #ifndef gc_ZoneAllocator_h
     12 #define gc_ZoneAllocator_h
     13 
     14 #include "mozilla/Maybe.h"
     15 
     16 #include "jsfriendapi.h"
     17 #include "jstypes.h"
     18 #include "gc/Cell.h"
     19 #include "gc/Scheduling.h"
     20 #include "js/GCAPI.h"
     21 #include "js/shadow/Zone.h"  // JS::shadow::Zone
     22 #include "vm/MallocProvider.h"
     23 
     24 namespace JS {
     25 class JS_PUBLIC_API Zone;
     26 }  // namespace JS
     27 
     28 namespace js {
     29 
     30 class ZoneAllocator;
     31 
     32 #ifdef DEBUG
     33 bool CurrentThreadIsGCFinalizing();
     34 #endif
     35 
     36 namespace gc {
     37 void MaybeMallocTriggerZoneGC(JSRuntime* rt, ZoneAllocator* zoneAlloc,
     38                              const HeapSize& heap,
     39                              const HeapThreshold& threshold,
     40                              JS::GCReason reason);
     41 }
     42 
     43 // Base class of JS::Zone that provides malloc memory allocation and accounting.
     44 class ZoneAllocator : public JS::shadow::Zone,
     45                      public js::MallocProvider<JS::Zone> {
     46 protected:
     47  explicit ZoneAllocator(JSRuntime* rt, Kind kind);
     48  ~ZoneAllocator();
     49  void fixupAfterMovingGC();
     50 
     51 public:
     52  static ZoneAllocator* from(JS::Zone* zone) {
     53    // This is a safe upcast, but the compiler hasn't seen the definition yet.
     54    return reinterpret_cast<ZoneAllocator*>(zone);
     55  }
     56 
     57  [[nodiscard]] void* onOutOfMemory(js::AllocFunction allocFunc,
     58                                    arena_id_t arena, size_t nbytes,
     59                                    void* reallocPtr = nullptr);
     60  void reportAllocationOverflow() const;
     61 
     62  void updateSchedulingStateOnGCStart();
     63  void updateGCStartThresholds(gc::GCRuntime& gc);
     64  void setGCSliceThresholds(gc::GCRuntime& gc, bool waitingOnBGTask);
     65  void clearGCSliceThresholds();
     66 
     67  // Memory accounting APIs for malloc memory owned by GC cells.
     68 
     69  void addCellMemory(js::gc::Cell* cell, size_t nbytes, js::MemoryUse use) {
     70    MOZ_ASSERT(cell);
     71    MOZ_ASSERT(nbytes);
     72 
     73    mallocHeapSize.addBytes(nbytes);
     74 
     75 #ifdef DEBUG
     76    mallocTracker.trackGCMemory(cell, nbytes, use);
     77 #endif
     78 
     79    maybeTriggerGCOnMalloc();
     80  }
     81 
     82  void removeCellMemory(js::gc::Cell* cell, size_t nbytes, js::MemoryUse use,
     83                        bool updateRetainedSize = false) {
     84    MOZ_ASSERT(cell);
     85    MOZ_ASSERT(nbytes);
     86    MOZ_ASSERT_IF(CurrentThreadIsGCFinalizing(), updateRetainedSize);
     87 
     88    mallocHeapSize.removeBytes(nbytes, updateRetainedSize);
     89 
     90 #ifdef DEBUG
     91    mallocTracker.untrackGCMemory(cell, nbytes, use);
     92 #endif
     93  }
     94 
     95  void swapCellMemory(js::gc::Cell* a, js::gc::Cell* b, js::MemoryUse use) {
     96 #ifdef DEBUG
     97    mallocTracker.swapGCMemory(a, b, use);
     98 #endif
     99  }
    100 
    101  void registerNonGCMemory(void* mem, MemoryUse use) {
    102 #ifdef DEBUG
    103    return mallocTracker.registerNonGCMemory(mem, use);
    104 #endif
    105  }
    106  void unregisterNonGCMemory(void* mem, MemoryUse use) {
    107 #ifdef DEBUG
    108    return mallocTracker.unregisterNonGCMemory(mem, use);
    109 #endif
    110  }
    111  void moveOtherMemory(void* dst, void* src, MemoryUse use) {
    112 #ifdef DEBUG
    113    return mallocTracker.moveNonGCMemory(dst, src, use);
    114 #endif
    115  }
    116 
    117  void incNonGCMemory(void* mem, size_t nbytes, MemoryUse use) {
    118    MOZ_ASSERT(nbytes);
    119    mallocHeapSize.addBytes(nbytes);
    120 
    121 #ifdef DEBUG
    122    mallocTracker.incNonGCMemory(mem, nbytes, use);
    123 #endif
    124 
    125    maybeTriggerGCOnMalloc();
    126  }
    127  void decNonGCMemory(void* mem, size_t nbytes, MemoryUse use,
    128                      bool updateRetainedSize) {
    129    MOZ_ASSERT(nbytes);
    130 
    131    mallocHeapSize.removeBytes(nbytes, updateRetainedSize);
    132 
    133 #ifdef DEBUG
    134    mallocTracker.decNonGCMemory(mem, nbytes, use);
    135 #endif
    136  }
    137 
    138  // Account for allocations that may be referenced by more than one GC thing.
    139  bool addSharedMemory(void* mem, size_t nbytes, MemoryUse use);
    140  void removeSharedMemory(void* mem, size_t nbytes, MemoryUse use);
    141 
    142  void incJitMemory(size_t nbytes) {
    143    MOZ_ASSERT(nbytes);
    144    jitHeapSize.addBytes(nbytes);
    145    maybeTriggerZoneGC(jitHeapSize, jitHeapThreshold,
    146                       JS::GCReason::TOO_MUCH_JIT_CODE);
    147  }
    148  void decJitMemory(size_t nbytes) {
    149    MOZ_ASSERT(nbytes);
    150    jitHeapSize.removeBytes(nbytes, true);
    151  }
    152 
    153  // Check malloc allocation threshold and trigger a zone GC if necessary.
    154  void maybeTriggerGCOnMalloc() {
    155    maybeTriggerZoneGC(mallocHeapSize, mallocHeapThreshold,
    156                       JS::GCReason::TOO_MUCH_MALLOC);
    157  }
    158 
    159 private:
    160  void maybeTriggerZoneGC(const js::gc::HeapSize& heap,
    161                          const js::gc::HeapThreshold& threshold,
    162                          JS::GCReason reason) {
    163    if (heap.bytes() >= threshold.startBytes()) {
    164      gc::MaybeMallocTriggerZoneGC(runtimeFromAnyThread(), this, heap,
    165                                   threshold, reason);
    166    }
    167  }
    168 
    169  void updateCollectionRate(mozilla::TimeDuration mainThreadGCTime,
    170                            size_t initialBytesForAllZones);
    171 
    172  void updateAllocationRate(mozilla::TimeDuration mutatorTime);
    173 
    174 public:
    175  // The size of allocated GC arenas in this zone.
    176  gc::PerZoneGCHeapSize gcHeapSize;
    177 
    178  // Threshold used to trigger GC based on GC heap size.
    179  gc::GCHeapThreshold gcHeapThreshold;
    180 
    181  // Amount of malloc data owned by tenured GC things in this zone, including
    182  // external allocations supplied by JS::AddAssociatedMemory.
    183  gc::HeapSize mallocHeapSize;
    184 
    185  // Threshold used to trigger GC based on malloc allocations.
    186  gc::MallocHeapThreshold mallocHeapThreshold;
    187 
    188  // Amount of exectuable JIT code owned by GC things in this zone.
    189  gc::HeapSize jitHeapSize;
    190 
    191  // Threshold used to trigger GC based on JIT allocations.
    192  gc::JitHeapThreshold jitHeapThreshold;
    193 
    194  // Use counts for memory that can be referenced by more than one GC thing.
    195  // Memory recorded here is also recorded in mallocHeapSize.  This structure
    196  // is used to avoid over-counting in mallocHeapSize.
    197  gc::SharedMemoryMap sharedMemoryUseCounts;
    198 
    199  // Collection rate estimate for this zone in MB/s, and state used to calculate
    200  // it. Updated every time this zone is collected.
    201  MainThreadData<mozilla::Maybe<double>> smoothedCollectionRate;
    202  MainThreadOrGCTaskData<mozilla::TimeDuration> perZoneGCTime;
    203 
    204  // Allocation rate estimate in MB/s of mutator time, and state used to
    205  // calculate it.
    206  MainThreadData<mozilla::Maybe<double>> smoothedAllocationRate;
    207  MainThreadData<size_t> prevGCHeapSize;
    208 
    209 private:
    210 #ifdef DEBUG
    211  // In debug builds, malloc allocations can be tracked to make debugging easier
    212  // (possible?) if allocation and free sizes don't balance.
    213  gc::MemoryTracker mallocTracker;
    214 #endif
    215 
    216  friend class gc::GCRuntime;
    217 };
    218 
    219 // Whether memory is associated with a single cell or whether it is associated
    220 // with the zone as a whole (for memory used by the system).
    221 enum class TrackingKind { Cell, Zone };
    222 
    223 /*
    224 * Allocation policy that performs memory tracking for malloced memory. This
    225 * should be used for all containers associated with a GC thing or a zone.
    226 *
    227 * Since it doesn't hold a JSContext (those may not live long enough), it can't
    228 * report out-of-memory conditions itself; the caller must check for OOM and
    229 * take the appropriate action.
    230 *
    231 * FIXME bug 647103 - replace these *AllocPolicy names.
    232 */
    233 template <TrackingKind kind>
    234 class TrackedAllocPolicy : public MallocProvider<TrackedAllocPolicy<kind>> {
    235  ZoneAllocator* zone_;
    236 
    237 #ifdef DEBUG
    238  friend class js::gc::MemoryTracker;  // Can clear |zone_| on merge.
    239 #endif
    240 
    241 public:
    242  MOZ_IMPLICIT TrackedAllocPolicy(ZoneAllocator* z) : zone_(z) {
    243    zone()->registerNonGCMemory(this, MemoryUse::TrackedAllocPolicy);
    244  }
    245  MOZ_IMPLICIT TrackedAllocPolicy(JS::Zone* z)
    246      : TrackedAllocPolicy(ZoneAllocator::from(z)) {}
    247  TrackedAllocPolicy(TrackedAllocPolicy& other)
    248      : TrackedAllocPolicy(other.zone_) {}
    249  TrackedAllocPolicy(TrackedAllocPolicy&& other) : zone_(other.zone_) {
    250    zone()->moveOtherMemory(this, &other, MemoryUse::TrackedAllocPolicy);
    251    other.zone_ = nullptr;
    252  }
    253  ~TrackedAllocPolicy() {
    254    if (zone_) {
    255      zone_->unregisterNonGCMemory(this, MemoryUse::TrackedAllocPolicy);
    256    }
    257  }
    258 
    259  TrackedAllocPolicy& operator=(const TrackedAllocPolicy& other) {
    260    zone()->unregisterNonGCMemory(this, MemoryUse::TrackedAllocPolicy);
    261    zone_ = other.zone();
    262    zone()->registerNonGCMemory(this, MemoryUse::TrackedAllocPolicy);
    263    return *this;
    264  }
    265  TrackedAllocPolicy& operator=(TrackedAllocPolicy&& other) {
    266    MOZ_ASSERT(this != &other);
    267    zone()->unregisterNonGCMemory(this, MemoryUse::TrackedAllocPolicy);
    268    zone_ = other.zone();
    269    zone()->moveOtherMemory(this, &other, MemoryUse::TrackedAllocPolicy);
    270    other.zone_ = nullptr;
    271    return *this;
    272  }
    273 
    274  // Public methods required to fulfill the AllocPolicy interface.
    275 
    276  template <typename T>
    277  void free_(T* p, size_t numElems) {
    278    if (p) {
    279      decMemory(numElems * sizeof(T));
    280      js_free(p);
    281    }
    282  }
    283 
    284  [[nodiscard]] bool checkSimulatedOOM() const {
    285    return !js::oom::ShouldFailWithOOM();
    286  }
    287 
    288  void reportAllocOverflow() const { reportAllocationOverflow(); }
    289 
    290  // Internal methods called by the MallocProvider implementation.
    291 
    292  [[nodiscard]] void* onOutOfMemory(js::AllocFunction allocFunc,
    293                                    arena_id_t arena, size_t nbytes,
    294                                    void* reallocPtr = nullptr) {
    295    return zone()->onOutOfMemory(allocFunc, arena, nbytes, reallocPtr);
    296  }
    297  void reportAllocationOverflow() const { zone()->reportAllocationOverflow(); }
    298  void updateMallocCounter(size_t nbytes) {
    299    zone()->incNonGCMemory(this, nbytes, MemoryUse::TrackedAllocPolicy);
    300  }
    301 
    302 private:
    303  ZoneAllocator* zone() const {
    304    MOZ_ASSERT(zone_);
    305    return zone_;
    306  }
    307  void decMemory(size_t nbytes);
    308 };
    309 
    310 using ZoneAllocPolicy = TrackedAllocPolicy<TrackingKind::Zone>;
    311 using CellAllocPolicy = TrackedAllocPolicy<TrackingKind::Cell>;
    312 
    313 // Functions for memory accounting on the zone.
    314 
    315 // Associate malloc memory with a GC thing. This call should be matched by a
    316 // following call to RemoveCellMemory with the same size and use. The total
    317 // amount of malloc memory associated with a zone is used to trigger GC.
    318 //
    319 // You should use InitReservedSlot / InitObjectPrivate in preference to this
    320 // where possible.
    321 
    322 inline void AddCellMemory(gc::TenuredCell* cell, size_t nbytes, MemoryUse use) {
    323  if (nbytes) {
    324    ZoneAllocator::from(cell->zone())->addCellMemory(cell, nbytes, use);
    325  }
    326 }
    327 inline void AddCellMemory(gc::Cell* cell, size_t nbytes, MemoryUse use) {
    328  if (cell->isTenured()) {
    329    AddCellMemory(&cell->asTenured(), nbytes, use);
    330  }
    331 }
    332 
    333 // Remove association between malloc memory and a GC thing. This call should
    334 // follow a call to AddCellMemory with the same size and use.
    335 
    336 inline void RemoveCellMemory(gc::TenuredCell* cell, size_t nbytes,
    337                             MemoryUse use) {
    338  MOZ_ASSERT(!CurrentThreadIsGCFinalizing(),
    339             "Use GCContext methods to remove associated memory in finalizers");
    340 
    341  if (nbytes) {
    342    auto zoneBase = ZoneAllocator::from(cell->zoneFromAnyThread());
    343    zoneBase->removeCellMemory(cell, nbytes, use, false);
    344  }
    345 }
    346 inline void RemoveCellMemory(gc::Cell* cell, size_t nbytes, MemoryUse use) {
    347  if (cell->isTenured()) {
    348    RemoveCellMemory(&cell->asTenured(), nbytes, use);
    349  }
    350 }
    351 
    352 }  // namespace js
    353 
    354 #endif  // gc_ZoneAllocator_h