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
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
|
/* -*- 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/. */
/*
* GC-internal definitions of ArenaList and associated heap data structures.
*/
#ifndef gc_ArenaList_h
#define gc_ArenaList_h
#include "gc/AllocKind.h"
#include "js/GCAPI.h"
#include "js/HeapAPI.h"
#include "js/TypeDecls.h"
#include "threading/ProtectedData.h"
namespace js {
class Nursery;
class SliceBudget;
namespace gcstats {
struct Statistics;
}
namespace gc {
class Arena;
class BackgroundUnmarkTask;
struct FinalizePhase;
class FreeSpan;
class TenuredCell;
class TenuringTracer;
/*
* A single segment of a SortedArenaList. Each segment has a head and a tail,
* which track the start and end of a segment for O(1) append and concatenation.
*/
struct SortedArenaListSegment {
Arena* head;
Arena** tailp;
void clear() {
head = nullptr;
tailp = &head;
}
bool isEmpty() const { return tailp == &head; }
// Appends |arena| to this segment.
inline void append(Arena* arena);
// Points the tail of this segment at |arena|, which may be null. Note
// that this does not change the tail itself, but merely which arena
// follows it. This essentially turns the tail into a cursor (see also the
// description of ArenaList), but from the perspective of a SortedArenaList
// this makes no difference.
void linkTo(Arena* arena) { *tailp = arena; }
};
/*
* Arena lists contain a singly linked lists of arenas starting from a head
* pointer.
*
* They also have a cursor, which conceptually lies on arena boundaries,
* i.e. before the first arena, between two arenas, or after the last arena.
*
* Arenas are usually sorted in order of increasing free space, with the cursor
* following the Arena currently being allocated from. This ordering should not
* be treated as an invariant, however, as the free lists may be cleared,
* leaving arenas previously used for allocation partially full. Sorting order
* is restored during sweeping.
*
* Arenas following the cursor should not be full.
*/
class ArenaList {
// The cursor is implemented via an indirect pointer, |cursorp_|, to allow
// for efficient list insertion at the cursor point and other list
// manipulations.
//
// - If the list is empty: |head| is null, |cursorp_| points to |head|, and
// therefore |*cursorp_| is null.
//
// - If the list is not empty: |head| is non-null, and...
//
// - If the cursor is at the start of the list: |cursorp_| points to
// |head|, and therefore |*cursorp_| points to the first arena.
//
// - If cursor is at the end of the list: |cursorp_| points to the |next|
// field of the last arena, and therefore |*cursorp_| is null.
//
// - If the cursor is at neither the start nor the end of the list:
// |cursorp_| points to the |next| field of the arena preceding the
// cursor, and therefore |*cursorp_| points to the arena following the
// cursor.
//
// |cursorp_| is never null.
//
Arena* head_;
Arena** cursorp_;
// Transfers the contents of |other| to this list and clears |other|.
inline void moveFrom(ArenaList& other);
public:
inline ArenaList();
inline ArenaList(ArenaList&& other);
inline ~ArenaList();
inline ArenaList& operator=(ArenaList&& other);
// It doesn't make sense for arenas to be present in more than one list, so
// list copy operations are not provided.
ArenaList(const ArenaList& other) = delete;
ArenaList& operator=(const ArenaList& other) = delete;
inline explicit ArenaList(const SortedArenaListSegment& segment);
inline void check() const;
inline void clear();
inline bool isEmpty() const;
// This returns nullptr if the list is empty.
inline Arena* head() const;
inline bool isCursorAtHead() const;
inline bool isCursorAtEnd() const;
// This can return nullptr.
inline Arena* arenaAfterCursor() const;
// This returns the arena after the cursor and moves the cursor past it.
inline Arena* takeNextArena();
// This does two things.
// - Inserts |a| at the cursor.
// - Leaves the cursor sitting just before |a|, if |a| is not full, or just
// after |a|, if |a| is full.
inline void insertAtCursor(Arena* a);
// Inserts |a| at the cursor, then moves the cursor past it.
inline void insertBeforeCursor(Arena* a);
// This inserts the contents of |other|, which must be full, at the cursor of
// |this| and clears |other|.
inline ArenaList& insertListWithCursorAtEnd(ArenaList& other);
inline Arena* takeFirstArena();
Arena* removeRemainingArenas(Arena** arenap);
Arena** pickArenasToRelocate(size_t& arenaTotalOut, size_t& relocTotalOut);
Arena* relocateArenas(Arena* toRelocate, Arena* relocated,
js::SliceBudget& sliceBudget,
gcstats::Statistics& stats);
#ifdef DEBUG
void dump();
#endif
};
/*
* A class that holds arenas in sorted order by appending arenas to specific
* segments. Each segment has a head and a tail, which can be linked up to
* other segments to create a contiguous ArenaList.
*/
class SortedArenaList {
public:
// The minimum size, in bytes, of a GC thing.
static const size_t MinThingSize = 16;
static_assert(ArenaSize <= 4096,
"When increasing the Arena size, please consider how"
" this will affect the size of a SortedArenaList.");
static_assert(MinThingSize >= 16,
"When decreasing the minimum thing size, please consider"
" how this will affect the size of a SortedArenaList.");
private:
// The maximum number of GC things that an arena can hold.
static const size_t MaxThingsPerArena =
(ArenaSize - ArenaHeaderSize) / MinThingSize;
size_t thingsPerArena_;
SortedArenaListSegment segments[MaxThingsPerArena + 1];
// Convenience functions to get the nth head and tail.
Arena* headAt(size_t n) { return segments[n].head; }
Arena** tailAt(size_t n) { return segments[n].tailp; }
public:
inline explicit SortedArenaList(size_t thingsPerArena = MaxThingsPerArena);
inline void setThingsPerArena(size_t thingsPerArena);
// Resets the first |thingsPerArena| segments of this list for further use.
inline void reset(size_t thingsPerArena = MaxThingsPerArena);
// Inserts an arena, which has room for |nfree| more things, in its segment.
inline void insertAt(Arena* arena, size_t nfree);
// Remove all empty arenas, inserting them as a linked list.
inline void extractEmpty(Arena** empty);
// Links up the tail of each non-empty segment to the head of the next
// non-empty segment, creating a contiguous list that is returned as an
// ArenaList. This is not a destructive operation: neither the head nor tail
// of any segment is modified. However, note that the Arenas in the
// resulting ArenaList should be treated as read-only unless the
// SortedArenaList is no longer needed: inserting or removing arenas would
// invalidate the SortedArenaList.
inline ArenaList toArenaList();
};
enum class ShouldCheckThresholds {
DontCheckThresholds = 0,
CheckThresholds = 1
};
// For each arena kind its free list is represented as the first span with free
// things. Initially all the spans are initialized as empty. After we find a new
// arena with available things we move its first free span into the list and set
// the arena as fully allocated. That way we do not need to update the arena
// after the initial allocation. When starting the GC we only move the head of
// the of the list of spans back to the arena only for the arena that was not
// fully allocated.
class FreeLists {
AllAllocKindArray<FreeSpan*> freeLists_;
public:
// Because the JITs can allocate from the free lists, they cannot be null.
// We use a placeholder FreeSpan that is empty (and wihout an associated
// Arena) so the JITs can fall back gracefully.
static FreeSpan emptySentinel;
FreeLists();
#ifdef DEBUG
inline bool allEmpty() const;
inline bool isEmpty(AllocKind kind) const;
#endif
inline void clear();
MOZ_ALWAYS_INLINE TenuredCell* allocate(AllocKind kind);
inline void* setArenaAndAllocate(Arena* arena, AllocKind kind);
inline void unmarkPreMarkedFreeCells(AllocKind kind);
FreeSpan** addressOfFreeList(AllocKind thingKind) {
return &freeLists_[thingKind];
}
};
class ArenaLists {
enum class ConcurrentUse : uint32_t { None, BackgroundFinalize };
using ConcurrentUseState =
mozilla::Atomic<ConcurrentUse, mozilla::SequentiallyConsistent>;
JS::Zone* zone_;
// Whether this structure can be accessed by other threads.
UnprotectedData<AllAllocKindArray<ConcurrentUseState>> concurrentUseState_;
MainThreadData<FreeLists> freeLists_;
/* The main list of arenas for each alloc kind. */
MainThreadOrGCTaskData<AllAllocKindArray<ArenaList>> arenaLists_;
/*
* Arenas which are currently being collected. The collector can move arenas
* from arenaLists_ here and back again at various points in collection.
*/
MainThreadOrGCTaskData<AllAllocKindArray<ArenaList>> collectingArenaLists_;
/* During incremental sweeping, a list of the arenas already swept. */
MainThreadOrGCTaskData<AllocKind> incrementalSweptArenaKind;
MainThreadOrGCTaskData<ArenaList> incrementalSweptArenas;
// Arena lists which have yet to be swept, but need additional foreground
// processing before they are swept.
MainThreadData<Arena*> gcCompactPropMapArenasToUpdate;
MainThreadData<Arena*> gcNormalPropMapArenasToUpdate;
// The list of empty arenas which are collected during the sweep phase and
// released at the end of sweeping every sweep group.
MainThreadOrGCTaskData<Arena*> savedEmptyArenas;
public:
explicit ArenaLists(JS::Zone* zone);
~ArenaLists();
FreeLists& freeLists() { return freeLists_.ref(); }
const FreeLists& freeLists() const { return freeLists_.ref(); }
FreeSpan** addressOfFreeList(AllocKind thingKind) {
return freeLists_.refNoCheck().addressOfFreeList(thingKind);
}
inline Arena* getFirstArena(AllocKind thingKind) const;
inline Arena* getFirstCollectingArena(AllocKind thingKind) const;
inline Arena* getFirstSweptArena(AllocKind thingKind) const;
inline Arena* getArenaAfterCursor(AllocKind thingKind) const;
inline bool arenaListsAreEmpty() const;
inline bool doneBackgroundFinalize(AllocKind kind) const;
inline bool needBackgroundFinalizeWait(AllocKind kind) const;
/* Clear the free lists so we won't try to allocate from swept arenas. */
inline void clearFreeLists();
inline void unmarkPreMarkedFreeCells();
MOZ_ALWAYS_INLINE TenuredCell* allocateFromFreeList(AllocKind thingKind);
inline void checkEmptyFreeLists();
inline void checkEmptyArenaLists();
inline void checkEmptyFreeList(AllocKind kind);
void checkEmptyArenaList(AllocKind kind);
bool relocateArenas(Arena*& relocatedListOut, JS::GCReason reason,
js::SliceBudget& sliceBudget, gcstats::Statistics& stats);
void queueForegroundObjectsForSweep(JS::GCContext* gcx);
void queueForegroundThingsForSweep();
Arena* takeSweptEmptyArenas();
void setIncrementalSweptArenas(AllocKind kind, SortedArenaList& arenas);
void clearIncrementalSweptArenas();
void mergeFinalizedArenas(AllocKind thingKind,
SortedArenaList& finalizedArenas);
void moveArenasToCollectingLists();
void mergeArenasFromCollectingLists();
void checkGCStateNotInUse();
void checkSweepStateNotInUse();
void checkNoArenasToUpdate();
void checkNoArenasToUpdateForKind(AllocKind kind);
private:
ArenaList& arenaList(AllocKind i) { return arenaLists_.ref()[i]; }
const ArenaList& arenaList(AllocKind i) const { return arenaLists_.ref()[i]; }
ArenaList& collectingArenaList(AllocKind i) {
return collectingArenaLists_.ref()[i];
}
const ArenaList& collectingArenaList(AllocKind i) const {
return collectingArenaLists_.ref()[i];
}
ConcurrentUseState& concurrentUse(AllocKind i) {
return concurrentUseState_.ref()[i];
}
ConcurrentUse concurrentUse(AllocKind i) const {
return concurrentUseState_.ref()[i];
}
inline JSRuntime* runtime();
inline JSRuntime* runtimeFromAnyThread();
void initBackgroundSweep(AllocKind thingKind);
void* refillFreeListAndAllocate(AllocKind thingKind,
ShouldCheckThresholds checkThresholds);
friend class BackgroundUnmarkTask;
friend class GCRuntime;
friend class js::Nursery;
friend class TenuringTracer;
};
} /* namespace gc */
} /* namespace js */
#endif /* gc_ArenaList_h */
|