#ifndef gc_Zone_h
#define gc_Zone_h
#include "mozilla/Atomics.h"
#include "mozilla/HashFunctions.h"
#include "mozilla/SegmentedVector.h"
#include "gc/FindSCCs.h"
#include "js/GCHashTable.h"
#include "vm/MallocProvider.h"
#include "vm/Runtime.h"
#include "vm/TypeInference.h"
namespace js {
class Debugger;
class RegExpZone;
namespace jit {
class JitZone;
}
namespace gc {
using ZoneComponentFinder = ComponentFinder<JS::Zone>;
struct UniqueIdGCPolicy {
static bool needsSweep(Cell** cell, uint64_t* value);
};
using UniqueIdMap = GCHashMap<Cell*, uint64_t, PointerHasher<Cell*>,
SystemAllocPolicy, UniqueIdGCPolicy>;
extern uint64_t NextCellUniqueId(JSRuntime* rt);
template <typename T>
class ZoneAllCellIter;
template <typename T>
class ZoneCellIter;
}
class MOZ_NON_TEMPORARY_CLASS ExternalStringCache {
static const size_t NumEntries = 4;
mozilla::Array<JSString*, NumEntries> entries_;
ExternalStringCache(const ExternalStringCache&) = delete;
void operator=(const ExternalStringCache&) = delete;
public:
ExternalStringCache() { purge(); }
void purge() { mozilla::PodArrayZero(entries_); }
MOZ_ALWAYS_INLINE JSString* lookup(const char16_t* chars, size_t len) const;
MOZ_ALWAYS_INLINE void put(JSString* s);
};
class MOZ_NON_TEMPORARY_CLASS FunctionToStringCache {
struct Entry {
JSScript* script;
JSString* string;
void set(JSScript* scriptArg, JSString* stringArg) {
script = scriptArg;
string = stringArg;
}
};
static const size_t NumEntries = 2;
mozilla::Array<Entry, NumEntries> entries_;
FunctionToStringCache(const FunctionToStringCache&) = delete;
void operator=(const FunctionToStringCache&) = delete;
public:
FunctionToStringCache() { purge(); }
void purge() { mozilla::PodArrayZero(entries_); }
MOZ_ALWAYS_INLINE JSString* lookup(JSScript* script) const;
MOZ_ALWAYS_INLINE void put(JSScript* script, JSString* string);
};
}
namespace JS {
class Zone : public JS::shadow::Zone,
public js::gc::GraphNodeBase<JS::Zone>,
public js::MallocProvider<JS::Zone> {
public:
explicit Zone(JSRuntime* rt);
~Zone();
MOZ_MUST_USE bool init(bool isSystem);
void destroy(js::FreeOp* fop);
private:
enum class HelperThreadUse : uint32_t { None, Pending, Active };
mozilla::Atomic<HelperThreadUse, mozilla::SequentiallyConsistent,
mozilla::recordreplay::Behavior::DontPreserve>
helperThreadUse_;
js::UnprotectedData<JSContext*> helperThreadOwnerContext_;
public:
bool ownedByCurrentHelperThread();
void setHelperThreadOwnerContext(JSContext* cx);
bool createdForHelperThread() const {
return helperThreadUse_ != HelperThreadUse::None;
}
bool usedByHelperThread() {
MOZ_ASSERT_IF(isAtomsZone(), helperThreadUse_ == HelperThreadUse::None);
return helperThreadUse_ == HelperThreadUse::Active;
}
void setCreatedForHelperThread() {
MOZ_ASSERT(helperThreadUse_ == HelperThreadUse::None);
helperThreadUse_ = HelperThreadUse::Pending;
}
void setUsedByHelperThread() {
MOZ_ASSERT(helperThreadUse_ == HelperThreadUse::Pending);
helperThreadUse_ = HelperThreadUse::Active;
}
void clearUsedByHelperThread() {
MOZ_ASSERT(helperThreadUse_ != HelperThreadUse::None);
helperThreadUse_ = HelperThreadUse::None;
}
MOZ_MUST_USE bool findSweepGroupEdges(Zone* atomsZone);
enum ShouldDiscardBaselineCode : bool {
KeepBaselineCode = false,
DiscardBaselineCode
};
enum ShouldReleaseTypes : bool { KeepTypes = false, ReleaseTypes };
void discardJitCode(
js::FreeOp* fop,
ShouldDiscardBaselineCode discardBaselineCode = DiscardBaselineCode,
ShouldReleaseTypes releaseTypes = KeepTypes);
void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
size_t* typePool, size_t* regexpZone,
size_t* jitZone, size_t* baselineStubsOptimized,
size_t* cachedCFG, size_t* uniqueIdMap,
size_t* shapeCaches, size_t* atomsMarkBitmaps,
size_t* compartmentObjects,
size_t* crossCompartmentWrappersTables,
size_t* compartmentsPrivateData);
template <typename T, typename... Args>
js::gc::ZoneCellIter<T> cellIter(Args&&... args) {
return js::gc::ZoneCellIter<T>(const_cast<Zone*>(this),
std::forward<Args>(args)...);
}
template <typename T, typename... Args>
js::gc::ZoneAllCellIter<T> cellIterUnsafe(Args&&... args) {
return js::gc::ZoneAllCellIter<T>(const_cast<Zone*>(this),
std::forward<Args>(args)...);
}
MOZ_MUST_USE void* onOutOfMemory(js::AllocFunction allocFunc,
arena_id_t arena, size_t nbytes,
void* reallocPtr = nullptr);
void reportAllocationOverflow();
void beginSweepTypes();
bool hasMarkedRealms();
void scheduleGC() {
MOZ_ASSERT(!RuntimeHeapIsBusy());
gcScheduled_ = true;
}
void unscheduleGC() { gcScheduled_ = false; }
bool isGCScheduled() { return gcScheduled_; }
void setPreservingCode(bool preserving) { gcPreserveCode_ = preserving; }
bool isPreservingCode() const { return gcPreserveCode_; }
bool canCollect();
void changeGCState(GCState prev, GCState next) {
MOZ_ASSERT(RuntimeHeapIsBusy());
MOZ_ASSERT(gcState() == prev);
MOZ_ASSERT_IF(next != NoGC, canCollect());
gcState_ = next;
}
bool isCollecting() const {
MOZ_ASSERT(js::CurrentThreadCanAccessRuntime(runtimeFromMainThread()));
return isCollectingFromAnyThread();
}
bool isCollectingFromAnyThread() const {
if (RuntimeHeapIsCollecting()) {
return gcState_ != NoGC;
} else {
return needsIncrementalBarrier();
}
}
bool shouldMarkInZone() const {
return needsIncrementalBarrier() || isGCMarking();
}
uint64_t gcNumber();
void setNeedsIncrementalBarrier(bool needs);
const uint32_t* addressOfNeedsIncrementalBarrier() const {
return &needsIncrementalBarrier_;
}
static constexpr size_t offsetOfNeedsIncrementalBarrier() {
return offsetof(Zone, needsIncrementalBarrier_);
}
js::jit::JitZone* getJitZone(JSContext* cx) {
return jitZone_ ? jitZone_ : createJitZone(cx);
}
js::jit::JitZone* jitZone() { return jitZone_; }
bool isAtomsZone() const { return runtimeFromAnyThread()->isAtomsZone(this); }
bool isSelfHostingZone() const {
return runtimeFromAnyThread()->isSelfHostingZone(this);
}
void prepareForCompacting();
#ifdef DEBUG
bool requireGCTracer() const;
unsigned lastSweepGroupIndex() { return gcLastSweepGroupIndex; }
#endif
void sweepBreakpoints(js::FreeOp* fop);
void sweepUniqueIds();
void sweepWeakMaps();
void sweepCompartments(js::FreeOp* fop, bool keepAtleastOne, bool lastGC);
using DebuggerVector = js::Vector<js::Debugger*, 0, js::SystemAllocPolicy>;
private:
js::ZoneData<DebuggerVector*> debuggers;
js::jit::JitZone* createJitZone(JSContext* cx);
bool isQueuedForBackgroundSweep() { return isOnList(); }
js::ZoneOrGCTaskData<js::gc::UniqueIdMap> uniqueIds_;
js::gc::UniqueIdMap& uniqueIds() { return uniqueIds_.ref(); }
public:
bool hasDebuggers() const { return debuggers && debuggers->length(); }
DebuggerVector* getDebuggers() const { return debuggers; }
DebuggerVector* getOrCreateDebuggers(JSContext* cx);
void notifyObservingDebuggers();
void clearTables();
js::ZoneData<bool> suppressAllocationMetadataBuilder;
js::gc::ArenaLists arenas;
private:
mozilla::Atomic<uint32_t, mozilla::Relaxed,
mozilla::recordreplay::Behavior::DontPreserve>
tenuredAllocsSinceMinorGC_;
public:
void addTenuredAllocsSinceMinorGC(uint32_t allocs) {
tenuredAllocsSinceMinorGC_ += allocs;
}
uint32_t getAndResetTenuredAllocsSinceMinorGC() {
return tenuredAllocsSinceMinorGC_.exchange(0);
}
js::TypeZone types;
private:
js::ZoneOrGCTaskData<mozilla::LinkedList<js::WeakMapBase>> gcWeakMapList_;
public:
mozilla::LinkedList<js::WeakMapBase>& gcWeakMapList() {
return gcWeakMapList_.ref();
}
typedef js::Vector<JS::Compartment*, 1, js::SystemAllocPolicy>
CompartmentVector;
private:
js::MainThreadOrGCTaskData<CompartmentVector> compartments_;
public:
CompartmentVector& compartments() { return compartments_.ref(); }
using GrayRootVector =
mozilla::SegmentedVector<js::gc::Cell*, 1024 * sizeof(js::gc::Cell*),
js::SystemAllocPolicy>;
private:
js::ZoneOrGCTaskData<GrayRootVector> gcGrayRoots_;
public:
GrayRootVector& gcGrayRoots() { return gcGrayRoots_.ref(); }
using WeakEdges = js::Vector<js::gc::TenuredCell**, 0, js::SystemAllocPolicy>;
private:
js::ZoneOrGCTaskData<WeakEdges> gcWeakRefs_;
public:
WeakEdges& gcWeakRefs() { return gcWeakRefs_.ref(); }
private:
js::ZoneOrGCTaskData<mozilla::LinkedList<detail::WeakCacheBase>> weakCaches_;
public:
mozilla::LinkedList<detail::WeakCacheBase>& weakCaches() {
return weakCaches_.ref();
}
void registerWeakCache(detail::WeakCacheBase* cachep) {
weakCaches().insertBack(cachep);
}
private:
js::ZoneOrGCTaskData<js::gc::WeakKeyTable> gcWeakKeys_;
public:
js::gc::WeakKeyTable& gcWeakKeys() { return gcWeakKeys_.ref(); }
NodeSet& gcSweepGroupEdges() {
return gcGraphEdges; }
MOZ_MUST_USE bool addSweepGroupEdgeTo(Zone* otherZone) {
MOZ_ASSERT(otherZone->isGCMarking());
return gcSweepGroupEdges().put(otherZone);
}
void clearSweepGroupEdges() { gcSweepGroupEdges().clear(); }
using TypeDescrObjectSet =
js::GCHashSet<JSObject*, js::MovableCellHasher<JSObject*>,
js::SystemAllocPolicy>;
private:
js::ZoneData<JS::WeakCache<TypeDescrObjectSet>> typeDescrObjects_;
js::gc::MemoryCounter gcMallocCounter;
js::gc::MemoryCounter jitCodeCounter;
void updateMemoryCounter(js::gc::MemoryCounter& counter, size_t nbytes) {
JSRuntime* rt = runtimeFromAnyThread();
counter.update(nbytes);
auto trigger = counter.shouldTriggerGC(rt->gc.tunables);
if (MOZ_LIKELY(trigger == js::gc::NoTrigger) ||
trigger <= counter.triggered()) {
return;
}
maybeTriggerGCForTooMuchMalloc(counter, trigger);
}
void maybeTriggerGCForTooMuchMalloc(js::gc::MemoryCounter& counter,
js::gc::TriggerKind trigger);
js::MainThreadData<js::UniquePtr<js::RegExpZone>> regExps_;
public:
js::RegExpZone& regExps() { return *regExps_.ref(); }
JS::WeakCache<TypeDescrObjectSet>& typeDescrObjects() {
return typeDescrObjects_.ref();
}
bool addTypeDescrObject(JSContext* cx, HandleObject obj);
void setGCMaxMallocBytes(size_t value, const js::AutoLockGC& lock) {
gcMallocCounter.setMax(value, lock);
}
void updateMallocCounter(size_t nbytes) {
updateMemoryCounter(gcMallocCounter, nbytes);
}
void adoptMallocBytes(Zone* other) {
gcMallocCounter.adopt(other->gcMallocCounter);
}
size_t GCMaxMallocBytes() const { return gcMallocCounter.maxBytes(); }
size_t GCMallocBytes() const { return gcMallocCounter.bytes(); }
void updateJitCodeMallocBytes(size_t nbytes) {
updateMemoryCounter(jitCodeCounter, nbytes);
}
void updateAllGCMallocCountersOnGCStart();
void updateAllGCMallocCountersOnGCEnd(const js::AutoLockGC& lock);
js::gc::TriggerKind shouldTriggerGCForTooMuchMalloc();
void keepAtoms() { keepAtomsCount++; }
void releaseAtoms();
bool hasKeptAtoms() const { return keepAtomsCount; }
private:
js::ZoneOrGCTaskData<js::SparseBitmap> markedAtoms_;
js::ZoneOrGCTaskData<js::AtomSet> atomCache_;
js::ZoneOrGCTaskData<js::ExternalStringCache> externalStringCache_;
js::ZoneOrGCTaskData<js::FunctionToStringCache> functionToStringCache_;
js::ZoneOrGCTaskData<unsigned> keepAtomsCount;
js::ZoneOrGCTaskData<bool> purgeAtomsDeferred;
public:
js::SparseBitmap& markedAtoms() { return markedAtoms_.ref(); }
js::AtomSet& atomCache() { return atomCache_.ref(); }
void traceAtomCache(JSTracer* trc);
void purgeAtomCacheOrDefer();
void purgeAtomCache();
js::ExternalStringCache& externalStringCache() {
return externalStringCache_.ref();
};
js::FunctionToStringCache& functionToStringCache() {
return functionToStringCache_.ref();
}
js::gc::HeapSize zoneSize;
js::gc::ZoneHeapThreshold threshold;
js::UnprotectedData<size_t> gcDelayBytes;
js::ZoneData<uint32_t> tenuredStrings;
js::ZoneData<bool> allocNurseryStrings;
private:
js::ZoneData<js::PropertyTree> propertyTree_;
public:
js::PropertyTree& propertyTree() { return propertyTree_.ref(); }
private:
js::ZoneData<js::BaseShapeSet> baseShapes_;
public:
js::BaseShapeSet& baseShapes() { return baseShapes_.ref(); }
private:
js::ZoneData<js::InitialShapeSet> initialShapes_;
public:
js::InitialShapeSet& initialShapes() { return initialShapes_.ref(); }
private:
using NurseryShapeVector =
js::Vector<js::AccessorShape*, 0, js::SystemAllocPolicy>;
js::ZoneData<NurseryShapeVector> nurseryShapes_;
public:
NurseryShapeVector& nurseryShapes() { return nurseryShapes_.ref(); }
#ifdef JSGC_HASH_TABLE_CHECKS
void checkInitialShapesTableAfterMovingGC();
void checkBaseShapeTableAfterMovingGC();
#endif
void fixupInitialShapeTable();
void fixupAfterMovingGC();
js::ZoneData<void*> data;
js::ZoneData<bool> isSystem;
#ifdef DEBUG
js::MainThreadData<unsigned> gcLastSweepGroupIndex;
#endif
static js::HashNumber UniqueIdToHash(uint64_t uid);
MOZ_MUST_USE bool getHashCode(js::gc::Cell* cell, js::HashNumber* hashp);
MOZ_MUST_USE bool maybeGetUniqueId(js::gc::Cell* cell, uint64_t* uidp);
MOZ_MUST_USE bool getOrCreateUniqueId(js::gc::Cell* cell, uint64_t* uidp);
js::HashNumber getHashCodeInfallible(js::gc::Cell* cell);
uint64_t getUniqueIdInfallible(js::gc::Cell* cell);
MOZ_MUST_USE bool hasUniqueId(js::gc::Cell* cell);
void transferUniqueId(js::gc::Cell* tgt, js::gc::Cell* src);
void removeUniqueId(js::gc::Cell* cell);
void adoptUniqueIds(JS::Zone* source);
#ifdef JSGC_HASH_TABLE_CHECKS
void checkUniqueIdTableAfterMovingGC();
#endif
bool keepShapeCaches() const { return keepShapeCaches_; }
void setKeepShapeCaches(bool b) { keepShapeCaches_ = b; }
void deleteEmptyCompartment(JS::Compartment* comp);
uint32_t detachedTypedObjects = 0;
private:
js::ZoneOrGCTaskData<js::jit::JitZone*> jitZone_;
js::MainThreadData<bool> gcScheduled_;
js::MainThreadData<bool> gcScheduledSaved_;
js::MainThreadData<bool> gcPreserveCode_;
js::ZoneData<bool> keepShapeCaches_;
friend class js::gc::ZoneList;
static Zone* const NotOnList;
js::MainThreadOrGCTaskData<Zone*> listNext_;
bool isOnList() const;
Zone* nextZone() const;
friend bool js::CurrentThreadCanAccessZone(Zone* zone);
friend class js::gc::GCRuntime;
};
}
namespace js {
class ZoneAllocPolicy {
JS::Zone* const zone;
public:
MOZ_IMPLICIT ZoneAllocPolicy(JS::Zone* z) : zone(z) {}
template <typename T>
T* maybe_pod_malloc(size_t numElems) {
return zone->maybe_pod_malloc<T>(numElems);
}
template <typename T>
T* maybe_pod_calloc(size_t numElems) {
return zone->maybe_pod_calloc<T>(numElems);
}
template <typename T>
T* maybe_pod_realloc(T* p, size_t oldSize, size_t newSize) {
return zone->maybe_pod_realloc<T>(p, oldSize, newSize);
}
template <typename T>
T* pod_malloc(size_t numElems) {
return zone->pod_malloc<T>(numElems);
}
template <typename T>
T* pod_calloc(size_t numElems) {
return zone->pod_calloc<T>(numElems);
}
template <typename T>
T* pod_realloc(T* p, size_t oldSize, size_t newSize) {
return zone->pod_realloc<T>(p, oldSize, newSize);
}
template <typename T>
void free_(T* p, size_t numElems = 0) {
js_free(p);
}
void reportAllocOverflow() const {}
MOZ_MUST_USE bool checkSimulatedOOM() const {
return !js::oom::ShouldFailWithOOM();
}
};
}
#endif