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 */