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/. */
11 #include "mozilla/EnumeratedArray.h"
12 #include "mozilla/TimeStamp.h"
14 #include "gc/GCProbes.h"
16 #include "gc/MallocedBlockCache.h"
17 #include "gc/Pretenuring.h"
18 #include "js/AllocPolicy.h"
21 #include "js/TypeDecls.h"
22 #include "js/UniquePtr.h"
23 #include "js/Vector.h"
25 #define FOR_EACH_NURSERY_PROFILE_TIME(_) \
26 /* Key Header text */ \
28 _(TraceValues, "mkVals") \
29 _(TraceCells, "mkClls") \
30 _(TraceSlots, "mkSlts") \
31 _(TraceWasmAnyRefs, "mkWars") \
32 _(TraceWholeCells, "mcWCll") \
33 _(TraceGenericEntries, "mkGnrc") \
34 _(CheckHashTables, "ckTbls") \
35 _(MarkRuntime, "mkRntm") \
36 _(MarkDebugger, "mkDbgr") \
37 _(SweepCaches, "swpCch") \
38 _(CollectToObjFP, "colObj") \
39 _(CollectToStrFP, "colStr") \
40 _(ObjectsTenuredCallback, "tenCB") \
42 _(UpdateJitActivations, "updtIn") \
43 _(FreeMallocedBuffers, "frSlts") \
44 _(FreeTrailerBlocks, "frTrBs") \
45 _(ClearStoreBuffer, "clrSB") \
46 _(ClearNursery, "clear") \
47 _(PurgeStringToAtomCache, "pStoA") \
48 _(Pretenure, "pretnr")
56 class AutoLockGCBgAlloc
;
63 class JS_PUBLIC_API Sprinter
;
68 class GCSchedulingTunables
;
74 explicit Nursery(gc::GCRuntime
* gc
);
77 [[nodiscard
]] bool init(AutoLockGCBgAlloc
& lock
);
79 // Number of allocated (ready to use) chunks.
80 unsigned allocatedChunkCount() const { return chunks_
.length(); }
82 // Total number of chunks and the capacity of the nursery. Chunks will be
83 // lazilly allocated and added to the chunks array up to this limit, after
84 // that the nursery must be collected, this limit may be raised during
86 unsigned maxChunkCount() const {
87 MOZ_ASSERT(capacity());
88 return HowMany(capacity(), gc::ChunkSize
);
93 bool isEnabled() const { return capacity() != 0; }
96 void disableStrings();
97 bool canAllocateStrings() const { return canAllocateStrings_
; }
100 void disableBigInts();
101 bool canAllocateBigInts() const { return canAllocateBigInts_
; }
103 // Return true if no allocations have been made since the last collection.
104 bool isEmpty() const;
106 // Check whether an arbitrary pointer is within the nursery. This is
107 // slower than IsInsideNursery(Cell*), but works on all types of pointers.
108 MOZ_ALWAYS_INLINE
bool isInside(gc::Cell
* cellp
) const = delete;
109 MOZ_ALWAYS_INLINE
bool isInside(const void* p
) const {
110 for (auto* chunk
: chunks_
) {
111 if (uintptr_t(p
) - uintptr_t(chunk
) < gc::ChunkSize
) {
118 template <typename T
>
119 inline bool isInside(const SharedMem
<T
>& p
) const;
121 // Allocate and return a pointer to a new GC thing. Returns nullptr if the
123 void* allocateCell(gc::AllocSite
* site
, size_t size
, JS::TraceKind kind
);
125 // Allocate and return a pointer to a new GC thing. Returns nullptr if the
126 // handleAllocationFailure() needs to be called before retrying.
127 inline void* tryAllocateCell(gc::AllocSite
* site
, size_t size
,
130 // Attempt to handle the failure of tryAllocate. Returns a GCReason if minor
131 // GC is required, or NO_REASON if the failure was handled and allocation will
133 [[nodiscard
]] JS::GCReason
handleAllocationFailure();
135 static size_t nurseryCellHeaderSize() {
136 return sizeof(gc::NurseryCellHeader
);
139 // Allocate a buffer for a given GC thing, using the nursery if possible and
140 // |cell| is in the nursery.
141 void* allocateBuffer(JS::Zone
* zone
, gc::Cell
* cell
, size_t nbytes
);
143 // Allocate a buffer for a given object, always using the nursery if obj is
144 // in the nursery. The requested size must be less than or equal to
145 // MaxNurseryBufferSize.
146 void* allocateBufferSameLocation(gc::Cell
* cell
, size_t nbytes
);
148 // Allocate a zero-initialized buffer for a given zone, using the nursery if
149 // possible. If the buffer isn't allocated in the nursery, the given arena is
151 void* allocateZeroedBuffer(JS::Zone
* zone
, size_t nbytes
,
152 arena_id_t arena
= js::MallocArena
);
154 // Allocate a zero-initialized buffer for a given object, using the nursery if
155 // possible and obj is in the nursery. If the buffer isn't allocated in the
156 // nursery, the given arena is used.
157 void* allocateZeroedBuffer(gc::Cell
* cell
, size_t nbytes
,
158 arena_id_t arena
= js::MallocArena
);
160 // Resize an existing buffer.
161 void* reallocateBuffer(JS::Zone
* zone
, gc::Cell
* cell
, void* oldBuffer
,
162 size_t oldBytes
, size_t newBytes
);
164 // Free an object buffer.
165 void freeBuffer(void* buffer
, size_t nbytes
);
167 // The maximum number of bytes allowed to reside in nursery buffers.
168 static const size_t MaxNurseryBufferSize
= 1024;
170 // Do a minor collection.
171 void collect(JS::GCOptions options
, JS::GCReason reason
);
173 // If the thing at |*ref| in the Nursery has been forwarded, set |*ref| to
174 // the new location and return true. Otherwise return false and leave
176 [[nodiscard
]] MOZ_ALWAYS_INLINE
static bool getForwardedPointer(
179 // Forward a slots/elements pointer stored in an Ion frame.
180 void forwardBufferPointer(uintptr_t* pSlotsElems
);
182 inline void maybeSetForwardingPointer(JSTracer
* trc
, void* oldData
,
183 void* newData
, bool direct
);
184 inline void setForwardingPointerWhileTenuring(void* oldData
, void* newData
,
187 // Register a malloced buffer that is held by a nursery object, which
188 // should be freed at the end of a minor GC. Buffers are unregistered when
189 // their owning objects are tenured.
190 [[nodiscard
]] bool registerMallocedBuffer(void* buffer
, size_t nbytes
);
192 // Mark a malloced buffer as no longer needing to be freed.
193 void removeMallocedBuffer(void* buffer
, size_t nbytes
) {
194 MOZ_ASSERT(mallocedBuffers
.has(buffer
));
195 MOZ_ASSERT(nbytes
> 0);
196 MOZ_ASSERT(mallocedBufferBytes
>= nbytes
);
197 mallocedBuffers
.remove(buffer
);
198 mallocedBufferBytes
-= nbytes
;
201 // Mark a malloced buffer as no longer needing to be freed during minor
202 // GC. There's no need to account for the size here since all remaining
203 // buffers will soon be freed.
204 void removeMallocedBufferDuringMinorGC(void* buffer
) {
205 MOZ_ASSERT(JS::RuntimeHeapIsMinorCollecting());
206 MOZ_ASSERT(mallocedBuffers
.has(buffer
));
207 mallocedBuffers
.remove(buffer
);
210 [[nodiscard
]] bool addedUniqueIdToCell(gc::Cell
* cell
) {
211 MOZ_ASSERT(IsInsideNursery(cell
));
212 MOZ_ASSERT(isEnabled());
213 return cellsWithUid_
.append(cell
);
216 size_t sizeOfMallocedBuffers(mozilla::MallocSizeOf mallocSizeOf
) const;
218 // Wasm "trailer" (C++-heap-allocated) blocks.
220 // All involved blocks are allocated/deallocated via this nursery's
221 // `mallocedBlockCache_`. Hence we must store both the block address and
222 // its freelist ID, wrapped up in a PointerAndUint7.
224 // Trailer blocks registered here are added to `trailersAdded_`. Those that
225 // are later deregistered as a result of `obj_moved` calls that indicate
226 // tenuring, should be added to `trailersRemoved_`.
228 // Unfortunately ::unregisterTrailer cannot be allowed to OOM. To get
229 // around this we rely on the observation that all deregistered blocks
230 // should previously have been registered, so the deregistered set can never
231 // be larger than the registered set. Hence ::registerTrailer effectively
232 // preallocates space in `trailersRemoved_` so as to ensure that, in the
233 // worst case, all registered blocks can be handed to ::unregisterTrailer
234 // without needing to resize `trailersRemoved_` in ::unregisterTrailer.
236 // The downside is that most of the space in `trailersRemoved_` is wasted in
237 // the case where there are few blocks deregistered. This is unfortunate
238 // but it's hard to see how to avoid it.
240 // At the end of a minor collection, all blocks in the set `trailersAdded_ -
241 // trailersRemoved_[0 .. trailersRemovedUsed_ - 1]` are handed back to the
242 // `mallocedBlockCache_`.
243 [[nodiscard
]] inline bool registerTrailer(PointerAndUint7 blockAndListID
,
245 inline void unregisterTrailer(void* block
);
246 size_t sizeOfTrailerBlockSets(mozilla::MallocSizeOf mallocSizeOf
) const;
248 size_t capacity() const { return capacity_
; }
249 size_t committed() const {
250 return std::min(capacity_
, allocatedChunkCount() * gc::ChunkSize
);
253 // Used and free space both include chunk headers for that part of the
256 // usedSpace() + freeSpace() == capacity()
258 MOZ_ALWAYS_INLINE
size_t usedSpace() const {
259 return capacity() - freeSpace();
261 MOZ_ALWAYS_INLINE
size_t freeSpace() const {
262 MOZ_ASSERT(isEnabled());
263 MOZ_ASSERT(currentEnd_
- position_
<= NurseryChunkUsableSize
);
264 MOZ_ASSERT(currentChunk_
< maxChunkCount());
265 return (currentEnd_
- position_
) +
266 (maxChunkCount() - currentChunk_
- 1) * gc::ChunkSize
;
270 void enterZealMode();
271 void leaveZealMode();
274 // Write profile time JSON on JSONPrinter.
275 void renderProfileJSON(JSONPrinter
& json
) const;
277 // Print header line for profile times.
278 void printProfileHeader();
280 // Print total profile times on shutdown.
281 void printTotalProfileTimes();
283 void* addressOfPosition() const { return (void**)&position_
; }
284 static constexpr int32_t offsetOfCurrentEndFromPosition() {
285 return offsetof(Nursery
, currentEnd_
) - offsetof(Nursery
, position_
);
288 void* addressOfNurseryAllocatedSites() {
289 return pretenuringNursery
.addressOfAllocatedSites();
292 void requestMinorGC(JS::GCReason reason
);
294 bool minorGCRequested() const {
295 return minorGCTriggerReason_
!= JS::GCReason::NO_REASON
;
297 JS::GCReason
minorGCTriggerReason() const { return minorGCTriggerReason_
; }
299 bool shouldCollect() const;
300 bool isNearlyFull() const;
301 bool isUnderused() const;
303 bool enableProfiling() const { return enableProfiling_
; }
305 bool addMapWithNurseryMemory(MapObject
* obj
) {
306 MOZ_ASSERT_IF(!mapsWithNurseryMemory_
.empty(),
307 mapsWithNurseryMemory_
.back() != obj
);
308 return mapsWithNurseryMemory_
.append(obj
);
310 bool addSetWithNurseryMemory(SetObject
* obj
) {
311 MOZ_ASSERT_IF(!setsWithNurseryMemory_
.empty(),
312 setsWithNurseryMemory_
.back() != obj
);
313 return setsWithNurseryMemory_
.append(obj
);
316 // The amount of space in the mapped nursery available to allocations.
317 static const size_t NurseryChunkUsableSize
=
318 gc::ChunkSize
- sizeof(gc::ChunkBase
);
320 void joinDecommitTask();
322 mozilla::TimeStamp
collectionStartTime() {
323 return startTimes_
[ProfileKey::Total
];
326 bool canCreateAllocSite() { return pretenuringNursery
.canCreateAllocSite(); }
327 void noteAllocSiteCreated() { pretenuringNursery
.noteAllocSiteCreated(); }
328 bool reportPretenuring() const { return reportPretenuring_
; }
329 void maybeStopPretenuring(gc::GCRuntime
* gc
) {
330 pretenuringNursery
.maybeStopPretenuring(gc
);
333 void setAllocFlagsForZone(JS::Zone
* zone
);
335 // Round a size in bytes to the nearest valid nursery size.
336 static size_t roundSize(size_t size
);
338 // The malloc'd block cache.
339 gc::MallocedBlockCache
& mallocedBlockCache() { return mallocedBlockCache_
; }
340 size_t sizeOfMallocedBlockCache(mozilla::MallocSizeOf mallocSizeOf
) const {
341 return mallocedBlockCache_
.sizeOfExcludingThis(mallocSizeOf
);
344 mozilla::TimeStamp
lastCollectionEndTime() const;
347 // Fields used during allocation fast path are grouped first:
349 // Pointer to the first unallocated byte in the nursery.
352 // Pointer to the last byte of space in the current chunk.
353 uintptr_t currentEnd_
;
355 // Other fields not necessarily used during allocation follow:
357 gc::GCRuntime
* const gc
;
359 // Vector of allocated chunks to allocate from.
360 Vector
<NurseryChunk
*, 0, SystemAllocPolicy
> chunks_
;
362 // The index of the chunk that is currently being allocated from.
363 uint32_t currentChunk_
;
365 // These fields refer to the beginning of the nursery. They're normally 0
366 // and chunk(0).start() respectively. Except when a generational GC zeal
367 // mode is active, then they may be arbitrary (see Nursery::clear()).
368 uint32_t startChunk_
;
369 uintptr_t startPosition_
;
371 // The current nursery capacity measured in bytes. It may grow up to this
372 // value without a collection, allocating chunks on demand. This limit may be
373 // changed by maybeResizeNursery() each collection. It includes chunk headers.
376 gc::PretenuringNursery pretenuringNursery
;
378 mozilla::TimeDuration timeInChunkAlloc_
;
380 // Report minor collections taking at least this long, if enabled.
381 bool enableProfiling_
= false;
382 bool profileWorkers_
= false;
384 mozilla::TimeDuration profileThreshold_
;
386 // Whether we will nursery-allocate strings.
387 bool canAllocateStrings_
;
389 // Whether we will nursery-allocate BigInts.
390 bool canAllocateBigInts_
;
392 // Report how many strings were deduplicated.
393 bool reportDeduplications_
;
395 // Whether to report information on pretenuring, and if so the allocation
396 // threshold at which to report details of each allocation site.
397 bool reportPretenuring_
;
398 size_t reportPretenuringThreshold_
;
400 // Whether and why a collection of this nursery has been requested. When this
401 // happens |prevPosition_| is set to the current position and |position_| set
402 // to the end of the chunk to force the next allocation to fail.
403 JS::GCReason minorGCTriggerReason_
;
404 uintptr_t prevPosition_
;
408 enum class ProfileKey
{
409 #define DEFINE_TIME_KEY(name, text) name,
410 FOR_EACH_NURSERY_PROFILE_TIME(DEFINE_TIME_KEY
)
411 #undef DEFINE_TIME_KEY
416 mozilla::EnumeratedArray
<ProfileKey
, ProfileKey::KeyCount
,
418 using ProfileDurations
=
419 mozilla::EnumeratedArray
<ProfileKey
, ProfileKey::KeyCount
,
420 mozilla::TimeDuration
>;
422 ProfileTimes startTimes_
;
423 ProfileDurations profileDurations_
;
424 ProfileDurations totalDurations_
;
426 // Data about the previous collection.
428 JS::GCReason reason
= JS::GCReason::NO_REASON
;
429 size_t nurseryCapacity
= 0;
430 size_t nurseryCommitted
= 0;
431 size_t nurseryUsedBytes
= 0;
432 size_t nurseryUsedChunkCount
= 0;
433 size_t tenuredBytes
= 0;
434 size_t tenuredCells
= 0;
435 mozilla::TimeStamp endTime
;
437 PreviousGC previousGC
;
439 bool hasRecentGrowthData
;
440 double smoothedTargetSize
;
442 // Calculate the promotion rate of the most recent minor GC.
443 // The valid_for_tenuring parameter is used to return whether this
444 // promotion rate is accurate enough (the nursery was full enough) to be
445 // used for tenuring and other decisions.
447 // Must only be called if the previousGC data is initialised.
448 double calcPromotionRate(bool* validForTenuring
) const;
450 // The set of externally malloced buffers potentially kept live by objects
451 // stored in the nursery. Any external buffers that do not belong to a
452 // tenured thing at the end of a minor GC must be freed.
453 using BufferRelocationOverlay
= void*;
454 using BufferSet
= HashSet
<void*, PointerHasher
<void*>, SystemAllocPolicy
>;
455 BufferSet mallocedBuffers
;
456 size_t mallocedBufferBytes
= 0;
458 // Wasm "trailer" (C++-heap-allocated) blocks. See comments above on
459 // ::registerTrailer and ::unregisterTrailer.
460 Vector
<PointerAndUint7
, 0, SystemAllocPolicy
> trailersAdded_
;
461 Vector
<void*, 0, SystemAllocPolicy
> trailersRemoved_
;
462 size_t trailersRemovedUsed_
= 0;
463 size_t trailerBytes_
= 0;
465 void freeTrailerBlocks();
467 // During a collection most hoisted slot and element buffers indicate their
468 // new location with a forwarding pointer at the base. This does not work
469 // for buffers whose length is less than pointer width, or when different
470 // buffers might overlap each other. For these, an entry in the following
472 using ForwardedBufferMap
=
473 HashMap
<void*, void*, PointerHasher
<void*>, SystemAllocPolicy
>;
474 ForwardedBufferMap forwardedBuffers
;
476 // When we assign a unique id to cell in the nursery, that almost always
477 // means that the cell will be in a hash table, and thus, held live,
478 // automatically moving the uid from the nursery to its new home in
479 // tenured. It is possible, if rare, for an object that acquired a uid to
480 // be dead before the next collection, in which case we need to know to
481 // remove it when we sweep.
483 // Note: we store the pointers as Cell* here, resulting in an ugly cast in
484 // sweep. This is because this structure is used to help implement
485 // stable object hashing and we have to break the cycle somehow.
486 using CellsWithUniqueIdVector
= Vector
<gc::Cell
*, 8, SystemAllocPolicy
>;
487 CellsWithUniqueIdVector cellsWithUid_
;
489 // Lists of map and set objects allocated in the nursery or with iterators
490 // allocated there. Such objects need to be swept after minor GC.
491 Vector
<MapObject
*, 0, SystemAllocPolicy
> mapsWithNurseryMemory_
;
492 Vector
<SetObject
*, 0, SystemAllocPolicy
> setsWithNurseryMemory_
;
494 UniquePtr
<NurseryDecommitTask
> decommitTask
;
496 // A cache of small C++-heap allocated blocks associated with this Nursery.
497 // This provided so as to provide cheap allocation/deallocation of
498 // out-of-line storage areas as used by WasmStructObject and
499 // WasmArrayObject, although the mechanism is general and not specific to
500 // these object types. Regarding lifetimes, because the cache holds only
501 // blocks that are not currently in use, it can be flushed at any point with
502 // no correctness impact, only a performance impact.
503 gc::MallocedBlockCache mallocedBlockCache_
;
505 NurseryChunk
& chunk(unsigned index
) const { return *chunks_
[index
]; }
507 // Set the allocation position to the start of a chunk. This sets
508 // currentChunk_, position_ and currentEnd_ values as appropriate.
509 void moveToStartOfChunk(unsigned chunkno
);
511 bool initFirstChunk(AutoLockGCBgAlloc
& lock
);
513 // extent is advisory, it will be ignored in sub-chunk and generational zeal
514 // modes. It will be clamped to Min(NurseryChunkUsableSize, capacity_).
515 void poisonAndInitCurrentChunk(size_t extent
= gc::ChunkSize
);
517 void setCurrentEnd();
518 void setStartToCurrentPosition();
520 // Allocate the next chunk, or the first chunk for initialization.
521 // Callers will probably want to call moveToStartOfChunk(0) next.
522 [[nodiscard
]] bool allocateNextChunk(unsigned chunkno
,
523 AutoLockGCBgAlloc
& lock
);
525 uintptr_t currentEnd() const { return currentEnd_
; }
527 uintptr_t position() const { return position_
; }
529 MOZ_ALWAYS_INLINE
bool isSubChunkMode() const;
531 JSRuntime
* runtime() const;
532 gcstats::Statistics
& stats() const;
534 const js::gc::GCSchedulingTunables
& tunables() const;
536 void getAllocFlagsForZone(JS::Zone
* zone
, bool* allocObjectsOut
,
537 bool* allocStringsOut
, bool* allocBigIntsOut
);
538 void updateAllZoneAllocFlags();
539 void updateAllocFlagsForZone(JS::Zone
* zone
);
540 void discardCodeAndSetJitFlagsForZone(JS::Zone
* zone
);
542 void* allocate(size_t size
);
544 // Common internal allocator function. If this fails, call
545 // handleAllocationFailure to see whether it's possible to retry.
546 inline void* tryAllocate(size_t size
);
548 [[nodiscard
]] bool moveToNextChunk();
550 struct CollectionResult
{
554 CollectionResult
doCollection(gc::AutoGCSession
& session
,
555 JS::GCOptions options
, JS::GCReason reason
);
556 void traceRoots(gc::AutoGCSession
& session
, gc::TenuringTracer
& mover
);
558 size_t doPretenuring(JSRuntime
* rt
, JS::GCReason reason
,
559 bool validPromotionRate
, double promotionRate
);
561 // Handle relocation of slots/elements pointers stored in Ion frames.
562 inline void setForwardingPointer(void* oldData
, void* newData
, bool direct
);
564 inline void setDirectForwardingPointer(void* oldData
, void* newData
);
565 void setIndirectForwardingPointer(void* oldData
, void* newData
);
567 inline void setSlotsForwardingPointer(HeapSlot
* oldSlots
, HeapSlot
* newSlots
,
569 inline void setElementsForwardingPointer(ObjectElements
* oldHeader
,
570 ObjectElements
* newHeader
,
574 bool checkForwardingPointerLocation(void* ptr
, bool expectedInside
);
577 // Updates pointers to nursery objects that have been tenured and discards
578 // pointers to objects that have been freed.
581 // Reset the current chunk and position after a minor collection. Also poison
582 // the nursery on debug & nightly builds.
585 void sweepMapAndSetObjects();
587 // Allocate a buffer for a given zone, using the nursery if possible.
588 void* allocateBuffer(JS::Zone
* zone
, size_t nbytes
);
590 // Change the allocable space provided by the nursery.
591 void maybeResizeNursery(JS::GCOptions options
, JS::GCReason reason
);
592 size_t targetSize(JS::GCOptions options
, JS::GCReason reason
);
593 void clearRecentGrowthData();
594 void growAllocableSpace(size_t newCapacity
);
595 void shrinkAllocableSpace(size_t newCapacity
);
596 void minimizeAllocableSpace();
598 // Free the chunks starting at firstFreeChunk until the end of the chunks
599 // vector. Shrinks the vector but does not update maxChunkCount().
600 void freeChunksFrom(unsigned firstFreeChunk
);
602 void sendTelemetry(JS::GCReason reason
, mozilla::TimeDuration totalTime
,
603 bool wasEmpty
, double promotionRate
,
604 size_t sitesPretenured
);
606 void printCollectionProfile(JS::GCReason reason
, double promotionRate
);
607 void printDeduplicationData(js::StringStats
& prev
, js::StringStats
& curr
);
609 // Profile recording and printing.
610 void maybeClearProfileDurations();
611 void startProfile(ProfileKey key
);
612 void endProfile(ProfileKey key
);
613 static void printProfileDurations(const ProfileDurations
& times
,
616 mozilla::TimeStamp
collectionStartTime() const;
618 friend class gc::GCRuntime
;
619 friend class gc::TenuringTracer
;
620 friend struct NurseryChunk
;
625 #endif // gc_Nursery_h