diff options
Diffstat (limited to 'js/src/gc/ZoneAllocator.h')
-rw-r--r-- | js/src/gc/ZoneAllocator.h | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/js/src/gc/ZoneAllocator.h b/js/src/gc/ZoneAllocator.h new file mode 100644 index 0000000000..de2dd7da28 --- /dev/null +++ b/js/src/gc/ZoneAllocator.h @@ -0,0 +1,354 @@ +/* -*- 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/. */ + +/* + * Public header for allocating memory associated with GC things. + */ + +#ifndef gc_ZoneAllocator_h +#define gc_ZoneAllocator_h + +#include "mozilla/Maybe.h" + +#include "jsfriendapi.h" +#include "jstypes.h" +#include "gc/Cell.h" +#include "gc/Scheduling.h" +#include "js/GCAPI.h" +#include "js/shadow/Zone.h" // JS::shadow::Zone +#include "vm/MallocProvider.h" + +namespace JS { +class JS_PUBLIC_API Zone; +} // namespace JS + +namespace js { + +class ZoneAllocator; + +#ifdef DEBUG +bool CurrentThreadIsGCFinalizing(); +#endif + +namespace gc { +void MaybeMallocTriggerZoneGC(JSRuntime* rt, ZoneAllocator* zoneAlloc, + const HeapSize& heap, + const HeapThreshold& threshold, + JS::GCReason reason); +} + +// Base class of JS::Zone that provides malloc memory allocation and accounting. +class ZoneAllocator : public JS::shadow::Zone, + public js::MallocProvider<JS::Zone> { + protected: + explicit ZoneAllocator(JSRuntime* rt, Kind kind); + ~ZoneAllocator(); + void fixupAfterMovingGC(); + + public: + static ZoneAllocator* from(JS::Zone* zone) { + // This is a safe upcast, but the compiler hasn't seen the definition yet. + return reinterpret_cast<ZoneAllocator*>(zone); + } + + [[nodiscard]] void* onOutOfMemory(js::AllocFunction allocFunc, + arena_id_t arena, size_t nbytes, + void* reallocPtr = nullptr); + void reportAllocationOverflow() const; + + void updateSchedulingStateOnGCStart(); + void updateGCStartThresholds(gc::GCRuntime& gc); + void setGCSliceThresholds(gc::GCRuntime& gc, bool waitingOnBGTask); + void clearGCSliceThresholds(); + + // Memory accounting APIs for malloc memory owned by GC cells. + + void addCellMemory(js::gc::Cell* cell, size_t nbytes, js::MemoryUse use) { + MOZ_ASSERT(cell); + MOZ_ASSERT(nbytes); + + mallocHeapSize.addBytes(nbytes); + +#ifdef DEBUG + mallocTracker.trackGCMemory(cell, nbytes, use); +#endif + + maybeTriggerGCOnMalloc(); + } + + void removeCellMemory(js::gc::Cell* cell, size_t nbytes, js::MemoryUse use, + bool updateRetainedSize = false) { + MOZ_ASSERT(cell); + MOZ_ASSERT(nbytes); + MOZ_ASSERT_IF(CurrentThreadIsGCFinalizing(), updateRetainedSize); + + mallocHeapSize.removeBytes(nbytes, updateRetainedSize); + +#ifdef DEBUG + mallocTracker.untrackGCMemory(cell, nbytes, use); +#endif + } + + void swapCellMemory(js::gc::Cell* a, js::gc::Cell* b, js::MemoryUse use) { +#ifdef DEBUG + mallocTracker.swapGCMemory(a, b, use); +#endif + } + + void registerNonGCMemory(void* mem, MemoryUse use) { +#ifdef DEBUG + return mallocTracker.registerNonGCMemory(mem, use); +#endif + } + void unregisterNonGCMemory(void* mem, MemoryUse use) { +#ifdef DEBUG + return mallocTracker.unregisterNonGCMemory(mem, use); +#endif + } + void moveOtherMemory(void* dst, void* src, MemoryUse use) { +#ifdef DEBUG + return mallocTracker.moveNonGCMemory(dst, src, use); +#endif + } + + void incNonGCMemory(void* mem, size_t nbytes, MemoryUse use) { + MOZ_ASSERT(nbytes); + mallocHeapSize.addBytes(nbytes); + +#ifdef DEBUG + mallocTracker.incNonGCMemory(mem, nbytes, use); +#endif + + maybeTriggerGCOnMalloc(); + } + void decNonGCMemory(void* mem, size_t nbytes, MemoryUse use, + bool updateRetainedSize) { + MOZ_ASSERT(nbytes); + + mallocHeapSize.removeBytes(nbytes, updateRetainedSize); + +#ifdef DEBUG + mallocTracker.decNonGCMemory(mem, nbytes, use); +#endif + } + + // Account for allocations that may be referenced by more than one GC thing. + bool addSharedMemory(void* mem, size_t nbytes, MemoryUse use); + void removeSharedMemory(void* mem, size_t nbytes, MemoryUse use); + + void incJitMemory(size_t nbytes) { + MOZ_ASSERT(nbytes); + jitHeapSize.addBytes(nbytes); + maybeTriggerZoneGC(jitHeapSize, jitHeapThreshold, + JS::GCReason::TOO_MUCH_JIT_CODE); + } + void decJitMemory(size_t nbytes) { + MOZ_ASSERT(nbytes); + jitHeapSize.removeBytes(nbytes, true); + } + + // Check malloc allocation threshold and trigger a zone GC if necessary. + void maybeTriggerGCOnMalloc() { + maybeTriggerZoneGC(mallocHeapSize, mallocHeapThreshold, + JS::GCReason::TOO_MUCH_MALLOC); + } + + private: + void maybeTriggerZoneGC(const js::gc::HeapSize& heap, + const js::gc::HeapThreshold& threshold, + JS::GCReason reason) { + if (heap.bytes() >= threshold.startBytes()) { + gc::MaybeMallocTriggerZoneGC(runtimeFromAnyThread(), this, heap, + threshold, reason); + } + } + + void updateCollectionRate(mozilla::TimeDuration mainThreadGCTime, + size_t initialBytesForAllZones); + + void updateAllocationRate(mozilla::TimeDuration mutatorTime); + + public: + // The size of allocated GC arenas in this zone. + gc::PerZoneGCHeapSize gcHeapSize; + + // Threshold used to trigger GC based on GC heap size. + gc::GCHeapThreshold gcHeapThreshold; + + // Amount of malloc data owned by tenured GC things in this zone, including + // external allocations supplied by JS::AddAssociatedMemory. + gc::HeapSize mallocHeapSize; + + // Threshold used to trigger GC based on malloc allocations. + gc::MallocHeapThreshold mallocHeapThreshold; + + // Amount of exectuable JIT code owned by GC things in this zone. + gc::HeapSize jitHeapSize; + + // Threshold used to trigger GC based on JIT allocations. + gc::JitHeapThreshold jitHeapThreshold; + + // Use counts for memory that can be referenced by more than one GC thing. + // Memory recorded here is also recorded in mallocHeapSize. This structure + // is used to avoid over-counting in mallocHeapSize. + gc::SharedMemoryMap sharedMemoryUseCounts; + + // Collection rate estimate for this zone in MB/s, and state used to calculate + // it. Updated every time this zone is collected. + MainThreadData<mozilla::Maybe<double>> smoothedCollectionRate; + MainThreadOrGCTaskData<mozilla::TimeDuration> perZoneGCTime; + + // Allocation rate estimate in MB/s of mutator time, and state used to + // calculate it. + MainThreadData<mozilla::Maybe<double>> smoothedAllocationRate; + MainThreadData<size_t> prevGCHeapSize; + + private: +#ifdef DEBUG + // In debug builds, malloc allocations can be tracked to make debugging easier + // (possible?) if allocation and free sizes don't balance. + gc::MemoryTracker mallocTracker; +#endif + + friend class gc::GCRuntime; +}; + +// Whether memory is associated with a single cell or whether it is associated +// with the zone as a whole (for memory used by the system). +enum class TrackingKind { Cell, Zone }; + +/* + * Allocation policy that performs memory tracking for malloced memory. This + * should be used for all containers associated with a GC thing or a zone. + * + * Since it doesn't hold a JSContext (those may not live long enough), it can't + * report out-of-memory conditions itself; the caller must check for OOM and + * take the appropriate action. + * + * FIXME bug 647103 - replace these *AllocPolicy names. + */ +template <TrackingKind kind> +class TrackedAllocPolicy : public MallocProvider<TrackedAllocPolicy<kind>> { + ZoneAllocator* zone_; + +#ifdef DEBUG + friend class js::gc::MemoryTracker; // Can clear |zone_| on merge. +#endif + + public: + MOZ_IMPLICIT TrackedAllocPolicy(ZoneAllocator* z) : zone_(z) { + zone()->registerNonGCMemory(this, MemoryUse::TrackedAllocPolicy); + } + MOZ_IMPLICIT TrackedAllocPolicy(JS::Zone* z) + : TrackedAllocPolicy(ZoneAllocator::from(z)) {} + TrackedAllocPolicy(TrackedAllocPolicy& other) + : TrackedAllocPolicy(other.zone_) {} + TrackedAllocPolicy(TrackedAllocPolicy&& other) : zone_(other.zone_) { + zone()->moveOtherMemory(this, &other, MemoryUse::TrackedAllocPolicy); + other.zone_ = nullptr; + } + ~TrackedAllocPolicy() { + if (zone_) { + zone_->unregisterNonGCMemory(this, MemoryUse::TrackedAllocPolicy); + } + } + + TrackedAllocPolicy& operator=(const TrackedAllocPolicy& other) { + zone()->unregisterNonGCMemory(this, MemoryUse::TrackedAllocPolicy); + zone_ = other.zone(); + zone()->registerNonGCMemory(this, MemoryUse::TrackedAllocPolicy); + return *this; + } + TrackedAllocPolicy& operator=(TrackedAllocPolicy&& other) { + MOZ_ASSERT(this != &other); + zone()->unregisterNonGCMemory(this, MemoryUse::TrackedAllocPolicy); + zone_ = other.zone(); + zone()->moveOtherMemory(this, &other, MemoryUse::TrackedAllocPolicy); + other.zone_ = nullptr; + return *this; + } + + // Public methods required to fulfill the AllocPolicy interface. + + template <typename T> + void free_(T* p, size_t numElems) { + if (p) { + decMemory(numElems * sizeof(T)); + js_free(p); + } + } + + [[nodiscard]] bool checkSimulatedOOM() const { + return !js::oom::ShouldFailWithOOM(); + } + + void reportAllocOverflow() const { reportAllocationOverflow(); } + + // Internal methods called by the MallocProvider implementation. + + [[nodiscard]] void* onOutOfMemory(js::AllocFunction allocFunc, + arena_id_t arena, size_t nbytes, + void* reallocPtr = nullptr) { + return zone()->onOutOfMemory(allocFunc, arena, nbytes, reallocPtr); + } + void reportAllocationOverflow() const { zone()->reportAllocationOverflow(); } + void updateMallocCounter(size_t nbytes) { + zone()->incNonGCMemory(this, nbytes, MemoryUse::TrackedAllocPolicy); + } + + private: + ZoneAllocator* zone() const { + MOZ_ASSERT(zone_); + return zone_; + } + void decMemory(size_t nbytes); +}; + +using ZoneAllocPolicy = TrackedAllocPolicy<TrackingKind::Zone>; +using CellAllocPolicy = TrackedAllocPolicy<TrackingKind::Cell>; + +// Functions for memory accounting on the zone. + +// Associate malloc memory with a GC thing. This call should be matched by a +// following call to RemoveCellMemory with the same size and use. The total +// amount of malloc memory associated with a zone is used to trigger GC. +// +// You should use InitReservedSlot / InitObjectPrivate in preference to this +// where possible. + +inline void AddCellMemory(gc::TenuredCell* cell, size_t nbytes, MemoryUse use) { + if (nbytes) { + ZoneAllocator::from(cell->zone())->addCellMemory(cell, nbytes, use); + } +} +inline void AddCellMemory(gc::Cell* cell, size_t nbytes, MemoryUse use) { + if (cell->isTenured()) { + AddCellMemory(&cell->asTenured(), nbytes, use); + } +} + +// Remove association between malloc memory and a GC thing. This call should +// follow a call to AddCellMemory with the same size and use. + +inline void RemoveCellMemory(gc::TenuredCell* cell, size_t nbytes, + MemoryUse use) { + MOZ_ASSERT(!CurrentThreadIsGCFinalizing(), + "Use GCContext methods to remove associated memory in finalizers"); + + if (nbytes) { + auto zoneBase = ZoneAllocator::from(cell->zoneFromAnyThread()); + zoneBase->removeCellMemory(cell, nbytes, use, false); + } +} +inline void RemoveCellMemory(gc::Cell* cell, size_t nbytes, MemoryUse use) { + if (cell->isTenured()) { + RemoveCellMemory(&cell->asTenured(), nbytes, use); + } +} + +} // namespace js + +#endif // gc_ZoneAllocator_h |