1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sw=2 et tw=80:
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/. */
8 #include "gc/Pretenuring.h"
10 #include "mozilla/Sprintf.h"
12 #include "gc/GCInternals.h"
13 #include "gc/PublicIterators.h"
14 #include "jit/Invalidation.h"
16 #include "gc/PrivateIterators-inl.h"
17 #include "vm/JSScript-inl.h"
20 using namespace js::gc
;
22 // The number of nursery allocations at which to pay attention to an allocation
23 // site. This must be large enough to ensure we have enough information to infer
24 // the lifetime and also large enough to avoid pretenuring low volume allocation
26 static constexpr size_t AllocSiteAttentionThreshold
= 500;
28 // The maximum number of alloc sites to create between each minor
29 // collection. Stop tracking allocation after this limit is reached. This
30 // prevents unbounded time traversing the list during minor GC.
31 static constexpr size_t MaxAllocSitesPerMinorGC
= 500;
33 // The maximum number of times to invalidate JIT code for a site. After this we
34 // leave the site's state as Unknown and don't pretenure allocations.
35 // Note we use 4 bits to store the invalidation count.
36 static constexpr size_t MaxInvalidationCount
= 5;
38 // The minimum number of allocated cells needed to determine the survival rate
39 // of cells in newly created arenas.
40 static constexpr size_t MinCellsRequiredForSurvivalRate
= 100;
42 // The young survival rate below which a major collection is determined to have
43 // a low young survival rate.
44 static constexpr double LowYoungSurvivalThreshold
= 0.05;
46 // The number of consecutive major collections with a low young survival rate
47 // that must occur before recovery is attempted.
48 static constexpr size_t LowYoungSurvivalCountBeforeRecovery
= 2;
50 // The proportion of the nursery that must be tenured above which a minor
51 // collection may be determined to have a high nursery survival rate.
52 static constexpr double HighNurserySurvivalPromotionThreshold
= 0.6;
54 // The number of nursery allocations made by optimized JIT code that must be
55 // tenured above which a minor collection may be determined to have a high
56 // nursery survival rate.
57 static constexpr size_t HighNurserySurvivalOptimizedAllocThreshold
= 10000;
59 // The number of consecutive minor collections with a high nursery survival rate
60 // that must occur before recovery is attempted.
61 static constexpr size_t HighNurserySurvivalCountBeforeRecovery
= 2;
63 AllocSite
* const AllocSite::EndSentinel
= reinterpret_cast<AllocSite
*>(1);
64 JSScript
* const AllocSite::WasmScript
=
65 reinterpret_cast<JSScript
*>(AllocSite::STATE_MASK
+ 1);
67 static bool SiteBasedPretenuringEnabled
= true;
69 JS_PUBLIC_API
void JS::SetSiteBasedPretenuringEnabled(bool enable
) {
70 SiteBasedPretenuringEnabled
= enable
;
73 bool PretenuringNursery::canCreateAllocSite() {
74 MOZ_ASSERT(allocSitesCreated
<= MaxAllocSitesPerMinorGC
);
75 return SiteBasedPretenuringEnabled
&&
76 allocSitesCreated
< MaxAllocSitesPerMinorGC
;
79 size_t PretenuringNursery::doPretenuring(GCRuntime
* gc
, JS::GCReason reason
,
80 bool validPromotionRate
,
81 double promotionRate
, bool reportInfo
,
82 size_t reportThreshold
) {
83 size_t sitesActive
= 0;
84 size_t sitesPretenured
= 0;
85 size_t sitesInvalidated
= 0;
86 size_t zonesWithHighNurserySurvival
= 0;
88 // Zero allocation counts.
90 for (ZonesIter
zone(gc
, SkipAtoms
); !zone
.done(); zone
.next()) {
91 for (auto& count
: zone
->pretenuring
.nurseryAllocCounts
) {
96 // Check whether previously optimized code has changed its behaviour and
97 // needs to be recompiled so that it can pretenure its allocations.
98 if (validPromotionRate
) {
99 for (ZonesIter
zone(gc
, SkipAtoms
); !zone
.done(); zone
.next()) {
100 bool highNurserySurvivalRate
=
101 promotionRate
> HighNurserySurvivalPromotionThreshold
&&
102 zone
->optimizedAllocSite()->nurseryTenuredCount
>=
103 HighNurserySurvivalOptimizedAllocThreshold
;
104 zone
->pretenuring
.noteHighNurserySurvivalRate(highNurserySurvivalRate
);
105 if (highNurserySurvivalRate
) {
106 zonesWithHighNurserySurvival
++;
112 AllocSite::printInfoHeader(reason
, promotionRate
);
115 AllocSite
* site
= allocatedSites
;
116 allocatedSites
= AllocSite::EndSentinel
;
117 while (site
!= AllocSite::EndSentinel
) {
118 AllocSite
* next
= site
->nextNurseryAllocated
;
119 site
->nextNurseryAllocated
= nullptr;
121 MOZ_ASSERT_IF(site
->isNormal(),
122 site
->nurseryAllocCount
>= site
->nurseryTenuredCount
);
124 if (site
->isNormal()) {
125 processSite(gc
, site
, sitesActive
, sitesPretenured
, sitesInvalidated
,
126 reportInfo
, reportThreshold
);
132 // Catch-all sites don't end up on the list if they are only used from
133 // optimized JIT code, so process them here.
134 for (ZonesIter
zone(gc
, SkipAtoms
); !zone
.done(); zone
.next()) {
135 for (auto& site
: zone
->pretenuring
.unknownAllocSites
) {
136 processCatchAllSite(&site
, reportInfo
, reportThreshold
);
138 processCatchAllSite(zone
->optimizedAllocSite(), reportInfo
,
143 AllocSite::printInfoFooter(allocSitesCreated
, sitesActive
, sitesPretenured
,
145 if (zonesWithHighNurserySurvival
) {
146 fprintf(stderr
, " %zu zones with high nursery survival rate\n",
147 zonesWithHighNurserySurvival
);
151 allocSitesCreated
= 0;
153 return sitesPretenured
;
156 void PretenuringNursery::processSite(GCRuntime
* gc
, AllocSite
* site
,
158 size_t& sitesPretenured
,
159 size_t& sitesInvalidated
, bool reportInfo
,
160 size_t reportThreshold
) {
163 updateAllocCounts(site
);
165 bool hasPromotionRate
= false;
166 double promotionRate
= 0.0;
167 bool wasInvalidated
= false;
168 if (site
->nurseryAllocCount
> AllocSiteAttentionThreshold
) {
170 double(site
->nurseryTenuredCount
) / double(site
->nurseryAllocCount
);
171 hasPromotionRate
= true;
173 AllocSite::State prevState
= site
->state();
174 site
->updateStateOnMinorGC(promotionRate
);
175 AllocSite::State newState
= site
->state();
177 if (prevState
== AllocSite::State::Unknown
&&
178 newState
== AllocSite::State::LongLived
) {
181 // We can optimize JIT code before we realise that a site should be
182 // pretenured. Make sure we invalidate any existing optimized code.
183 if (site
->hasScript()) {
184 wasInvalidated
= site
->invalidateScript(gc
);
185 if (wasInvalidated
) {
192 if (reportInfo
&& site
->allocCount() >= reportThreshold
) {
193 site
->printInfo(hasPromotionRate
, promotionRate
, wasInvalidated
);
196 site
->resetNurseryAllocations();
199 void PretenuringNursery::processCatchAllSite(AllocSite
* site
, bool reportInfo
,
200 size_t reportThreshold
) {
201 if (!site
->hasNurseryAllocations()) {
205 updateAllocCounts(site
);
207 if (reportInfo
&& site
->allocCount() >= reportThreshold
) {
208 site
->printInfo(false, 0.0, false);
211 site
->resetNurseryAllocations();
214 void PretenuringNursery::updateAllocCounts(AllocSite
* site
) {
215 JS::TraceKind kind
= site
->traceKind();
216 totalAllocCount_
+= site
->nurseryAllocCount
;
217 PretenuringZone
& zone
= site
->zone()->pretenuring
;
218 zone
.nurseryAllocCount(kind
) += site
->nurseryAllocCount
;
221 bool AllocSite::invalidateScript(GCRuntime
* gc
) {
222 CancelOffThreadIonCompile(script());
224 if (!script()->hasIonScript()) {
228 if (invalidationLimitReached()) {
229 MOZ_ASSERT(state() == State::Unknown
);
234 if (invalidationLimitReached()) {
235 setState(State::Unknown
);
238 JSContext
* cx
= gc
->rt
->mainContextFromOwnThread();
239 jit::Invalidate(cx
, script(),
240 /* resetUses = */ false,
241 /* cancelOffThread = */ true);
245 bool AllocSite::invalidationLimitReached() const {
246 MOZ_ASSERT(invalidationCount
<= MaxInvalidationCount
);
247 return invalidationCount
== MaxInvalidationCount
;
250 void PretenuringNursery::maybeStopPretenuring(GCRuntime
* gc
) {
251 for (GCZonesIter
zone(gc
); !zone
.done(); zone
.next()) {
253 if (zone
->pretenuring
.calculateYoungTenuredSurvivalRate(&rate
)) {
254 bool lowYoungSurvivalRate
= rate
< LowYoungSurvivalThreshold
;
255 zone
->pretenuring
.noteLowYoungTenuredSurvivalRate(lowYoungSurvivalRate
);
260 AllocSite::Kind
AllocSite::kind() const {
265 if (this == zone()->optimizedAllocSite()) {
266 return Kind::Optimized
;
269 MOZ_ASSERT(this == zone()->unknownAllocSite(traceKind()));
270 return Kind::Unknown
;
273 void AllocSite::updateStateOnMinorGC(double promotionRate
) {
274 // The state changes based on whether the promotion rate is deemed high
275 // (greater that 90%):
278 // ------------------> ------------------>
279 // ShortLived Unknown LongLived
280 // <------------------ <------------------
283 // The nursery is used to allocate if the site's state is Unknown or
284 // ShortLived. There are no direct transition between ShortLived and LongLived
285 // to avoid pretenuring sites that we've recently observed being short-lived.
287 if (invalidationLimitReached()) {
288 MOZ_ASSERT(state() == State::Unknown
);
292 bool highPromotionRate
= promotionRate
>= 0.9;
296 if (highPromotionRate
) {
297 setState(State::LongLived
);
299 setState(State::ShortLived
);
303 case State::ShortLived
: {
304 if (highPromotionRate
) {
305 setState(State::Unknown
);
310 case State::LongLived
: {
311 if (!highPromotionRate
) {
312 setState(State::Unknown
);
319 bool AllocSite::maybeResetState() {
320 if (invalidationLimitReached()) {
321 MOZ_ASSERT(state() == State::Unknown
);
326 setState(State::Unknown
);
330 void AllocSite::trace(JSTracer
* trc
) {
331 if (JSScript
* s
= script()) {
332 TraceManuallyBarrieredEdge(trc
, &s
, "AllocSite script");
339 bool PretenuringZone::calculateYoungTenuredSurvivalRate(double* rateOut
) {
340 MOZ_ASSERT(allocCountInNewlyCreatedArenas
>=
341 survivorCountInNewlyCreatedArenas
);
342 if (allocCountInNewlyCreatedArenas
< MinCellsRequiredForSurvivalRate
) {
346 *rateOut
= double(survivorCountInNewlyCreatedArenas
) /
347 double(allocCountInNewlyCreatedArenas
);
351 void PretenuringZone::noteLowYoungTenuredSurvivalRate(
352 bool lowYoungSurvivalRate
) {
353 if (lowYoungSurvivalRate
) {
354 lowYoungTenuredSurvivalCount
++;
356 lowYoungTenuredSurvivalCount
= 0;
360 void PretenuringZone::noteHighNurserySurvivalRate(
361 bool highNurserySurvivalRate
) {
362 if (highNurserySurvivalRate
) {
363 highNurserySurvivalCount
++;
365 highNurserySurvivalCount
= 0;
369 bool PretenuringZone::shouldResetNurseryAllocSites() {
371 highNurserySurvivalCount
>= HighNurserySurvivalCountBeforeRecovery
;
373 highNurserySurvivalCount
= 0;
378 bool PretenuringZone::shouldResetPretenuredAllocSites() {
380 lowYoungTenuredSurvivalCount
>= LowYoungSurvivalCountBeforeRecovery
;
382 lowYoungTenuredSurvivalCount
= 0;
388 void AllocSite::printInfoHeader(JS::GCReason reason
, double promotionRate
) {
390 "Pretenuring info after %s minor GC with %4.1f%% promotion rate:\n",
391 ExplainGCReason(reason
), promotionRate
* 100.0);
395 void AllocSite::printInfoFooter(size_t sitesCreated
, size_t sitesActive
,
396 size_t sitesPretenured
,
397 size_t sitesInvalidated
) {
399 " %zu alloc sites created, %zu active, %zu pretenured, %zu "
401 sitesCreated
, sitesActive
, sitesPretenured
, sitesInvalidated
);
404 void AllocSite::printInfo(bool hasPromotionRate
, double promotionRate
,
405 bool wasInvalidated
) const {
407 fprintf(stderr
, " %p %p", this, zone());
409 // Script, or which kind of catch-all site this is.
411 fprintf(stderr
, " %16s",
412 kind() == Kind::Optimized
414 : (kind() == Kind::Normal
? "normal" : "unknown"));
416 fprintf(stderr
, " %16p", script());
419 // Nursery allocation count, missing for optimized sites.
420 char buffer
[16] = {'\0'};
421 if (kind() != Kind::Optimized
) {
422 SprintfLiteral(buffer
, "%8" PRIu32
, nurseryAllocCount
);
424 fprintf(stderr
, " %8s", buffer
);
426 // Nursery tenure count.
427 fprintf(stderr
, " %8" PRIu32
, nurseryTenuredCount
);
429 // Promotion rate, if there were enough allocations.
431 if (hasPromotionRate
) {
432 SprintfLiteral(buffer
, "%5.1f%%", std::min(1.0, promotionRate
) * 100);
434 fprintf(stderr
, " %6s", buffer
);
436 // Current state for sites associated with a script.
437 const char* state
= isNormal() ? stateName() : "";
438 fprintf(stderr
, " %10s", state
);
440 // Whether the associated script was invalidated.
441 if (wasInvalidated
) {
442 fprintf(stderr
, " invalidated");
445 fprintf(stderr
, "\n");
448 const char* AllocSite::stateName() const {
450 case State::ShortLived
:
454 case State::LongLived
:
458 MOZ_CRASH("Unknown state");