/* -*- 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 { 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(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> smoothedCollectionRate; MainThreadOrGCTaskData perZoneGCTime; // Allocation rate estimate in MB/s of mutator time, and state used to // calculate it. MainThreadData> smoothedAllocationRate; MainThreadData 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 class TrackedAllocPolicy : public MallocProvider> { 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 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; using CellAllocPolicy = TrackedAllocPolicy; // 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