1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* Pretenuring.
*
* Some kinds of GC cells can be allocated in either the nursery or the tenured
* heap. The pretenuring system decides where to allocate such cells based on
* their expected lifetime with the aim of minimising total collection time.
*
* Lifetime is predicted based on data gathered about the cells' allocation
* site. This data is gathered in the middle JIT tiers, after code has stopped
* executing in the interpreter and before we generate fully optimized code.
*/
#ifndef gc_Pretenuring_h
#define gc_Pretenuring_h
#include <algorithm>
#include "gc/AllocKind.h"
#include "js/TypeDecls.h"
class JS_PUBLIC_API JSTracer;
namespace JS {
enum class GCReason;
} // namespace JS
namespace js {
namespace gc {
class GCRuntime;
class PretenuringNursery;
enum class CatchAllAllocSite { Unknown, Optimized };
// Information about an allocation site.
//
// Nursery cells contain a pointer to one of these in their cell header (stored
// before the cell). The site can relate to either for a specific bytecode
// instruction or can be a catch-all instance for unknown sites or optimized
// code.
class AllocSite {
public:
enum class State : uint32_t { ShortLived = 0, Unknown = 1, LongLived = 2 };
// The JIT depends on being able to tell the states apart by checking a single
// bit.
static constexpr int32_t LONG_LIVED_BIT = int32_t(State::LongLived);
static_assert((LONG_LIVED_BIT & int32_t(State::Unknown)) == 0);
static_assert((AllocSite::LONG_LIVED_BIT & int32_t(State::ShortLived)) == 0);
private:
JS::Zone* const zone_;
// Word storing JSScript pointer and site state.
//
// The script pointer is the script that owns this allocation site or null for
// unknown sites. This is used when we need to invalidate the script.
uintptr_t scriptAndState = uintptr_t(State::Unknown);
static constexpr uintptr_t STATE_MASK = BitMask(2);
// Next pointer forming a linked list of sites at which nursery allocation
// happened since the last nursery collection.
AllocSite* nextNurseryAllocated = nullptr;
// Number of nursery allocations at this site since last nursery collection.
uint32_t nurseryAllocCount = 0;
// Number of nursery allocations that survived. Used during collection.
uint32_t nurseryTenuredCount : 24;
// Number of times the script has been invalidated.
uint32_t invalidationCount : 8;
static AllocSite* const EndSentinel;
friend class PretenuringZone;
friend class PretenuringNursery;
public:
// Create a dummy site to use for unknown allocations.
explicit AllocSite(JS::Zone* zone)
: zone_(zone), nurseryTenuredCount(0), invalidationCount(0) {}
// Create a site for an opcode in the given script.
AllocSite(JS::Zone* zone, JSScript* script) : AllocSite(zone) {
setScript(script);
}
JS::Zone* zone() const { return zone_; }
State state() const { return State(scriptAndState & STATE_MASK); }
JSScript* script() const {
return reinterpret_cast<JSScript*>(scriptAndState & ~STATE_MASK);
}
bool hasScript() const { return script(); }
enum class Kind : uint32_t { Normal, Unknown, Optimized };
Kind kind() const;
bool isInAllocatedList() const { return nextNurseryAllocated; }
// Whether allocations at this site should be allocated in the nursery or the
// tenured heap.
InitialHeap initialHeap() const {
return state() == State::LongLived ? TenuredHeap : DefaultHeap;
}
bool hasNurseryAllocations() const {
return nurseryAllocCount != 0 || nurseryTenuredCount != 0;
}
void resetNurseryAllocations() {
nurseryAllocCount = 0;
nurseryTenuredCount = 0;
}
void incAllocCount() { nurseryAllocCount++; }
void incTenuredCount() {
// The nursery is not large enough for this to overflow.
nurseryTenuredCount++;
MOZ_ASSERT(nurseryTenuredCount != 0);
}
size_t allocCount() const {
return std::max(nurseryAllocCount, nurseryTenuredCount);
}
void updateStateOnMinorGC(double promotionRate);
// Reset the state to 'Unknown' unless we have reached the invalidation limit
// for this site. Return whether the state was reset.
bool maybeResetState();
bool invalidationLimitReached() const;
bool invalidateScript(GCRuntime* gc);
void trace(JSTracer* trc);
static void printInfoHeader(JS::GCReason reason, double promotionRate);
static void printInfoFooter(size_t sitesCreated, size_t sitesActive,
size_t sitesPretenured, size_t sitesInvalidated);
void printInfo(bool hasPromotionRate, double promotionRate,
bool wasInvalidated) const;
static constexpr size_t offsetOfScriptAndState() {
return offsetof(AllocSite, scriptAndState);
}
static constexpr size_t offsetOfNurseryAllocCount() {
return offsetof(AllocSite, nurseryAllocCount);
}
static constexpr size_t offsetOfNextNurseryAllocated() {
return offsetof(AllocSite, nextNurseryAllocated);
}
private:
void setScript(JSScript* newScript) {
MOZ_ASSERT((uintptr_t(newScript) & STATE_MASK) == 0);
scriptAndState = uintptr_t(newScript) | uintptr_t(state());
}
void setState(State newState) {
MOZ_ASSERT((uintptr_t(newState) & ~STATE_MASK) == 0);
scriptAndState = uintptr_t(script()) | uintptr_t(newState);
}
const char* stateName() const;
};
// Pretenuring information stored per zone.
class PretenuringZone {
public:
explicit PretenuringZone(JS::Zone* zone)
: unknownAllocSite(zone), optimizedAllocSite(zone) {}
// Catch-all allocation site instance used when the actual site is unknown, or
// when optimized JIT code allocates a GC thing that's not handled by the
// pretenuring system.
AllocSite unknownAllocSite;
// Catch-all allocation instance used by optimized JIT code when allocating GC
// things that are handled by the pretenuring system. Allocation counts are
// not recorded by optimized JIT code.
AllocSite optimizedAllocSite;
// Count of tenured cell allocations made between each major collection and
// how many survived.
uint32_t allocCountInNewlyCreatedArenas = 0;
uint32_t survivorCountInNewlyCreatedArenas = 0;
// Count of successive collections that had a low young tenured survival
// rate. Used to discard optimized code if we get the pretenuring decision
// wrong.
uint32_t lowYoungTenuredSurvivalCount = 0;
// Count of successive nursery collections that had a high survival rate for
// objects allocated by optimized code. Used to discard optimized code if we
// get the pretenuring decision wrong.
uint32_t highNurserySurvivalCount = 0;
void clearCellCountsInNewlyCreatedArenas() {
allocCountInNewlyCreatedArenas = 0;
survivorCountInNewlyCreatedArenas = 0;
}
void updateCellCountsInNewlyCreatedArenas(uint32_t allocCount,
uint32_t survivorCount) {
allocCountInNewlyCreatedArenas += allocCount;
survivorCountInNewlyCreatedArenas += survivorCount;
}
bool calculateYoungTenuredSurvivalRate(double* rateOut);
void noteLowYoungTenuredSurvivalRate(bool lowYoungSurvivalRate);
void noteHighNurserySurvivalRate(bool highNurserySurvivalRate);
// Recovery: if code behaviour change we may need to reset allocation site
// state and invalidate JIT code.
bool shouldResetNurseryAllocSites();
bool shouldResetPretenuredAllocSites();
};
// Pretenuring information stored as part of the the GC nursery.
class PretenuringNursery {
gc::AllocSite* allocatedSites;
size_t allocSitesCreated = 0;
public:
PretenuringNursery() : allocatedSites(AllocSite::EndSentinel) {}
bool hasAllocatedSites() const {
return allocatedSites != AllocSite::EndSentinel;
}
bool canCreateAllocSite();
void noteAllocSiteCreated() { allocSitesCreated++; }
void insertIntoAllocatedList(AllocSite* site) {
MOZ_ASSERT(!site->isInAllocatedList());
site->nextNurseryAllocated = allocatedSites;
allocatedSites = site;
}
size_t doPretenuring(GCRuntime* gc, JS::GCReason reason,
bool validPromotionRate, double promotionRate,
bool reportInfo, size_t reportThreshold);
void maybeStopPretenuring(GCRuntime* gc);
void* addressOfAllocatedSites() { return &allocatedSites; }
private:
void reportAndResetCatchAllSite(AllocSite* site, bool reportInfo,
size_t reportThreshold);
};
} // namespace gc
} // namespace js
#endif /* gc_Pretenuring_h */
|