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