summaryrefslogtreecommitdiffstats
path: root/js/src/gc/Nursery.h
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/gc/Nursery.h')
-rw-r--r--js/src/gc/Nursery.h471
1 files changed, 471 insertions, 0 deletions
diff --git a/js/src/gc/Nursery.h b/js/src/gc/Nursery.h
new file mode 100644
index 000000000..69fb66b7a
--- /dev/null
+++ b/js/src/gc/Nursery.h
@@ -0,0 +1,471 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sw=4 et tw=78:
+ *
+ * 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/. */
+
+#ifndef gc_Nursery_h
+#define gc_Nursery_h
+
+#include "mozilla/EnumeratedArray.h"
+
+#include "jsalloc.h"
+#include "jspubtd.h"
+
+#include "ds/BitArray.h"
+#include "gc/Heap.h"
+#include "gc/Memory.h"
+#include "js/Class.h"
+#include "js/GCAPI.h"
+#include "js/HashTable.h"
+#include "js/HeapAPI.h"
+#include "js/Value.h"
+#include "js/Vector.h"
+#include "vm/SharedMem.h"
+
+#define FOR_EACH_NURSERY_PROFILE_TIME(_) \
+ /* Key Header text */ \
+ _(Total, "total") \
+ _(CancelIonCompilations, "canIon") \
+ _(TraceValues, "mkVals") \
+ _(TraceCells, "mkClls") \
+ _(TraceSlots, "mkSlts") \
+ _(TraceWholeCells, "mcWCll") \
+ _(TraceGenericEntries, "mkGnrc") \
+ _(CheckHashTables, "ckTbls") \
+ _(MarkRuntime, "mkRntm") \
+ _(MarkDebugger, "mkDbgr") \
+ _(ClearNewObjectCache, "clrNOC") \
+ _(CollectToFP, "collct") \
+ _(ObjectsTenuredCallback, "tenCB") \
+ _(SweepArrayBufferViewList, "swpABO") \
+ _(UpdateJitActivations, "updtIn") \
+ _(FreeMallocedBuffers, "frSlts") \
+ _(ClearStoreBuffer, "clrSB") \
+ _(Sweep, "sweep") \
+ _(Resize, "resize") \
+ _(Pretenure, "pretnr")
+
+namespace JS {
+struct Zone;
+} // namespace JS
+
+namespace js {
+
+class ObjectElements;
+class NativeObject;
+class Nursery;
+class HeapSlot;
+
+void SetGCZeal(JSRuntime*, uint8_t, uint32_t);
+
+namespace gc {
+class AutoMaybeStartBackgroundAllocation;
+struct Cell;
+class MinorCollectionTracer;
+class RelocationOverlay;
+struct TenureCountCache;
+} /* namespace gc */
+
+namespace jit {
+class MacroAssembler;
+} // namespace jit
+
+class TenuringTracer : public JSTracer
+{
+ friend class Nursery;
+ Nursery& nursery_;
+
+ // Amount of data moved to the tenured generation during collection.
+ size_t tenuredSize;
+
+ // This list is threaded through the Nursery using the space from already
+ // moved things. The list is used to fix up the moved things and to find
+ // things held live by intra-Nursery pointers.
+ gc::RelocationOverlay* head;
+ gc::RelocationOverlay** tail;
+
+ TenuringTracer(JSRuntime* rt, Nursery* nursery);
+
+ public:
+ const Nursery& nursery() const { return nursery_; }
+
+ // Returns true if the pointer was updated.
+ template <typename T> void traverse(T** thingp);
+ template <typename T> void traverse(T* thingp);
+
+ void insertIntoFixupList(gc::RelocationOverlay* entry);
+
+ // The store buffers need to be able to call these directly.
+ void traceObject(JSObject* src);
+ void traceObjectSlots(NativeObject* nobj, uint32_t start, uint32_t length);
+ void traceSlots(JS::Value* vp, uint32_t nslots) { traceSlots(vp, vp + nslots); }
+
+ private:
+ Nursery& nursery() { return nursery_; }
+
+ JSObject* moveToTenured(JSObject* src);
+ size_t moveObjectToTenured(JSObject* dst, JSObject* src, gc::AllocKind dstKind);
+ size_t moveElementsToTenured(NativeObject* dst, NativeObject* src, gc::AllocKind dstKind);
+ size_t moveSlotsToTenured(NativeObject* dst, NativeObject* src, gc::AllocKind dstKind);
+
+ void traceSlots(JS::Value* vp, JS::Value* end);
+};
+
+/*
+ * Classes with JSCLASS_SKIP_NURSERY_FINALIZE or Wrapper classes with
+ * CROSS_COMPARTMENT flags will not have their finalizer called if they are
+ * nursery allocated and not promoted to the tenured heap. The finalizers for
+ * these classes must do nothing except free data which was allocated via
+ * Nursery::allocateBuffer.
+ */
+inline bool
+CanNurseryAllocateFinalizedClass(const js::Class* const clasp)
+{
+ MOZ_ASSERT(clasp->hasFinalize());
+ return clasp->flags & JSCLASS_SKIP_NURSERY_FINALIZE;
+}
+
+class Nursery
+{
+ public:
+ static const size_t Alignment = gc::ChunkSize;
+ static const size_t ChunkShift = gc::ChunkShift;
+
+ explicit Nursery(JSRuntime* rt);
+ ~Nursery();
+
+ MOZ_MUST_USE bool init(uint32_t maxNurseryBytes, AutoLockGC& lock);
+
+ unsigned maxChunks() const { return maxNurseryChunks_; }
+ unsigned numChunks() const { return chunks_.length(); }
+
+ bool exists() const { return maxChunks() != 0; }
+ size_t nurserySize() const { return maxChunks() << ChunkShift; }
+
+ void enable();
+ void disable();
+ bool isEnabled() const { return numChunks() != 0; }
+
+ /* Return true if no allocations have been made since the last collection. */
+ bool isEmpty() const;
+
+ /*
+ * Check whether an arbitrary pointer is within the nursery. This is
+ * slower than IsInsideNursery(Cell*), but works on all types of pointers.
+ */
+ MOZ_ALWAYS_INLINE bool isInside(gc::Cell* cellp) const = delete;
+ MOZ_ALWAYS_INLINE bool isInside(const void* p) const {
+ for (auto chunk : chunks_) {
+ if (uintptr_t(p) - chunk->start() < gc::ChunkSize)
+ return true;
+ }
+ return false;
+ }
+ template<typename T>
+ bool isInside(const SharedMem<T>& p) const {
+ return isInside(p.unwrap(/*safe - used for value in comparison above*/));
+ }
+
+ /*
+ * Allocate and return a pointer to a new GC object with its |slots|
+ * pointer pre-filled. Returns nullptr if the Nursery is full.
+ */
+ JSObject* allocateObject(JSContext* cx, size_t size, size_t numDynamic, const js::Class* clasp);
+
+ /* Allocate a buffer for a given zone, using the nursery if possible. */
+ void* allocateBuffer(JS::Zone* zone, size_t nbytes);
+
+ /*
+ * Allocate a buffer for a given object, using the nursery if possible and
+ * obj is in the nursery.
+ */
+ void* allocateBuffer(JSObject* obj, size_t nbytes);
+
+ /* Resize an existing object buffer. */
+ void* reallocateBuffer(JSObject* obj, void* oldBuffer,
+ size_t oldBytes, size_t newBytes);
+
+ /* Free an object buffer. */
+ void freeBuffer(void* buffer);
+
+ /* The maximum number of bytes allowed to reside in nursery buffers. */
+ static const size_t MaxNurseryBufferSize = 1024;
+
+ /* Do a minor collection. */
+ void collect(JSRuntime* rt, JS::gcreason::Reason reason);
+
+ /*
+ * Check if the thing at |*ref| in the Nursery has been forwarded. If so,
+ * sets |*ref| to the new location of the object and returns true. Otherwise
+ * returns false and leaves |*ref| unset.
+ */
+ MOZ_ALWAYS_INLINE MOZ_MUST_USE bool getForwardedPointer(JSObject** ref) const;
+
+ /* Forward a slots/elements pointer stored in an Ion frame. */
+ void forwardBufferPointer(HeapSlot** pSlotsElems);
+
+ void maybeSetForwardingPointer(JSTracer* trc, void* oldData, void* newData, bool direct) {
+ if (trc->isTenuringTracer() && isInside(oldData))
+ setForwardingPointer(oldData, newData, direct);
+ }
+
+ /* Mark a malloced buffer as no longer needing to be freed. */
+ void removeMallocedBuffer(void* buffer) {
+ mallocedBuffers.remove(buffer);
+ }
+
+ void waitBackgroundFreeEnd();
+
+ MOZ_MUST_USE bool addedUniqueIdToCell(gc::Cell* cell) {
+ if (!IsInsideNursery(cell) || !isEnabled())
+ return true;
+ MOZ_ASSERT(cellsWithUid_.initialized());
+ MOZ_ASSERT(!cellsWithUid_.has(cell));
+ return cellsWithUid_.put(cell);
+ }
+
+ using SweepThunk = void (*)(void *data);
+ void queueSweepAction(SweepThunk thunk, void* data);
+
+ MOZ_MUST_USE bool queueDictionaryModeObjectToSweep(NativeObject* obj);
+
+ size_t sizeOfHeapCommitted() const {
+ return numChunks() * gc::ChunkSize;
+ }
+ size_t sizeOfMallocedBuffers(mozilla::MallocSizeOf mallocSizeOf) const {
+ size_t total = 0;
+ for (MallocedBuffersSet::Range r = mallocedBuffers.all(); !r.empty(); r.popFront())
+ total += mallocSizeOf(r.front());
+ total += mallocedBuffers.sizeOfExcludingThis(mallocSizeOf);
+ return total;
+ }
+
+ // The number of bytes from the start position to the end of the nursery.
+ size_t spaceToEnd() const;
+
+ // Free space remaining, not counting chunk trailers.
+ MOZ_ALWAYS_INLINE size_t freeSpace() const {
+ MOZ_ASSERT(currentEnd_ - position_ <= NurseryChunkUsableSize);
+ return (currentEnd_ - position_) +
+ (numChunks() - currentChunk_ - 1) * NurseryChunkUsableSize;
+ }
+
+#ifdef JS_GC_ZEAL
+ void enterZealMode();
+ void leaveZealMode();
+#endif
+
+ /* Print total profile times on shutdown. */
+ void printTotalProfileTimes();
+
+ private:
+ /* The amount of space in the mapped nursery available to allocations. */
+ static const size_t NurseryChunkUsableSize = gc::ChunkSize - sizeof(gc::ChunkTrailer);
+
+ struct NurseryChunk {
+ char data[NurseryChunkUsableSize];
+ gc::ChunkTrailer trailer;
+ static NurseryChunk* fromChunk(gc::Chunk* chunk);
+ void init(JSRuntime* rt);
+ void poisonAndInit(JSRuntime* rt, uint8_t poison);
+ uintptr_t start() const { return uintptr_t(&data); }
+ uintptr_t end() const { return uintptr_t(&trailer); }
+ gc::Chunk* toChunk(JSRuntime* rt);
+ };
+ static_assert(sizeof(NurseryChunk) == gc::ChunkSize,
+ "Nursery chunk size must match gc::Chunk size.");
+
+ /*
+ * The start and end pointers are stored under the runtime so that we can
+ * inline the isInsideNursery check into embedder code. Use the start()
+ * and heapEnd() functions to access these values.
+ */
+ JSRuntime* runtime_;
+
+ /* Vector of allocated chunks to allocate from. */
+ Vector<NurseryChunk*, 0, SystemAllocPolicy> chunks_;
+
+ /* Pointer to the first unallocated byte in the nursery. */
+ uintptr_t position_;
+
+ /* Pointer to the logical start of the Nursery. */
+ unsigned currentStartChunk_;
+ uintptr_t currentStartPosition_;
+
+ /* Pointer to the last byte of space in the current chunk. */
+ uintptr_t currentEnd_;
+
+ /* The index of the chunk that is currently being allocated from. */
+ unsigned currentChunk_;
+
+ /* Maximum number of chunks to allocate for the nursery. */
+ unsigned maxNurseryChunks_;
+
+ /* Promotion rate for the previous minor collection. */
+ double previousPromotionRate_;
+
+ /* Report minor collections taking at least this many us, if enabled. */
+ int64_t profileThreshold_;
+ bool enableProfiling_;
+
+ /* Report ObjectGroups with at lest this many instances tenured. */
+ int64_t reportTenurings_;
+
+ /* Profiling data. */
+
+ enum class ProfileKey
+ {
+#define DEFINE_TIME_KEY(name, text) \
+ name,
+ FOR_EACH_NURSERY_PROFILE_TIME(DEFINE_TIME_KEY)
+#undef DEFINE_TIME_KEY
+ KeyCount
+ };
+
+ using ProfileTimes = mozilla::EnumeratedArray<ProfileKey, ProfileKey::KeyCount, int64_t>;
+
+ ProfileTimes startTimes_;
+ ProfileTimes profileTimes_;
+ ProfileTimes totalTimes_;
+ uint64_t minorGcCount_;
+
+ /*
+ * The set of externally malloced buffers potentially kept live by objects
+ * stored in the nursery. Any external buffers that do not belong to a
+ * tenured thing at the end of a minor GC must be freed.
+ */
+ typedef HashSet<void*, PointerHasher<void*, 3>, SystemAllocPolicy> MallocedBuffersSet;
+ MallocedBuffersSet mallocedBuffers;
+
+ /* A task structure used to free the malloced bufers on a background thread. */
+ struct FreeMallocedBuffersTask;
+ FreeMallocedBuffersTask* freeMallocedBuffersTask;
+
+ /*
+ * During a collection most hoisted slot and element buffers indicate their
+ * new location with a forwarding pointer at the base. This does not work
+ * for buffers whose length is less than pointer width, or when different
+ * buffers might overlap each other. For these, an entry in the following
+ * table is used.
+ */
+ typedef HashMap<void*, void*, PointerHasher<void*, 1>, SystemAllocPolicy> ForwardedBufferMap;
+ ForwardedBufferMap forwardedBuffers;
+
+ /*
+ * When we assign a unique id to cell in the nursery, that almost always
+ * means that the cell will be in a hash table, and thus, held live,
+ * automatically moving the uid from the nursery to its new home in
+ * tenured. It is possible, if rare, for an object that acquired a uid to
+ * be dead before the next collection, in which case we need to know to
+ * remove it when we sweep.
+ *
+ * Note: we store the pointers as Cell* here, resulting in an ugly cast in
+ * sweep. This is because this structure is used to help implement
+ * stable object hashing and we have to break the cycle somehow.
+ */
+ using CellsWithUniqueIdSet = HashSet<gc::Cell*, PointerHasher<gc::Cell*, 3>, SystemAllocPolicy>;
+ CellsWithUniqueIdSet cellsWithUid_;
+
+ struct SweepAction;
+ SweepAction* sweepActions_;
+
+ using NativeObjectVector = Vector<NativeObject*, 0, SystemAllocPolicy>;
+ NativeObjectVector dictionaryModeObjects_;
+
+#ifdef JS_GC_ZEAL
+ struct Canary;
+ Canary* lastCanary_;
+#endif
+
+ NurseryChunk* allocChunk();
+
+ NurseryChunk& chunk(unsigned index) const {
+ return *chunks_[index];
+ }
+
+ void setCurrentChunk(unsigned chunkno);
+ void setStartPosition();
+
+ void updateNumChunks(unsigned newCount);
+ void updateNumChunksLocked(unsigned newCount,
+ gc::AutoMaybeStartBackgroundAllocation& maybeBgAlloc,
+ AutoLockGC& lock);
+
+ MOZ_ALWAYS_INLINE uintptr_t allocationEnd() const {
+ MOZ_ASSERT(numChunks() > 0);
+ return chunks_.back()->end();
+ }
+
+ MOZ_ALWAYS_INLINE uintptr_t currentEnd() const {
+ MOZ_ASSERT(runtime_);
+ MOZ_ASSERT(currentEnd_ == chunk(currentChunk_).end());
+ return currentEnd_;
+ }
+ void* addressOfCurrentEnd() const {
+ MOZ_ASSERT(runtime_);
+ return (void*)&currentEnd_;
+ }
+
+ uintptr_t position() const { return position_; }
+ void* addressOfPosition() const { return (void*)&position_; }
+
+ JSRuntime* runtime() const { return runtime_; }
+
+ /* Allocates a new GC thing from the tenured generation during minor GC. */
+ gc::TenuredCell* allocateFromTenured(JS::Zone* zone, gc::AllocKind thingKind);
+
+ /* Common internal allocator function. */
+ void* allocate(size_t size);
+
+ double doCollection(JSRuntime* rt, JS::gcreason::Reason reason,
+ gc::TenureCountCache& tenureCounts);
+
+ /*
+ * Move the object at |src| in the Nursery to an already-allocated cell
+ * |dst| in Tenured.
+ */
+ void collectToFixedPoint(TenuringTracer& trc, gc::TenureCountCache& tenureCounts);
+
+ /* Handle relocation of slots/elements pointers stored in Ion frames. */
+ void setForwardingPointer(void* oldData, void* newData, bool direct);
+
+ void setSlotsForwardingPointer(HeapSlot* oldSlots, HeapSlot* newSlots, uint32_t nslots);
+ void setElementsForwardingPointer(ObjectElements* oldHeader, ObjectElements* newHeader,
+ uint32_t nelems);
+
+ /* Free malloced pointers owned by freed things in the nursery. */
+ void freeMallocedBuffers();
+
+ /*
+ * Frees all non-live nursery-allocated things at the end of a minor
+ * collection.
+ */
+ void sweep();
+
+ void runSweepActions();
+ void sweepDictionaryModeObjects();
+
+ /* Change the allocable space provided by the nursery. */
+ void maybeResizeNursery(JS::gcreason::Reason reason, double promotionRate);
+ void growAllocableSpace();
+ void shrinkAllocableSpace();
+ void minimizeAllocableSpace();
+
+ /* Profile recording and printing. */
+ void startProfile(ProfileKey key);
+ void endProfile(ProfileKey key);
+ void maybeStartProfile(ProfileKey key);
+ void maybeEndProfile(ProfileKey key);
+ static void printProfileHeader();
+ static void printProfileTimes(const ProfileTimes& times);
+
+ friend class TenuringTracer;
+ friend class gc::MinorCollectionTracer;
+ friend class jit::MacroAssembler;
+};
+
+} /* namespace js */
+
+#endif /* gc_Nursery_h */