/* -*- 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/. */ #ifndef gc_Zone_h #define gc_Zone_h #include "mozilla/Atomics.h" #include "mozilla/HashFunctions.h" #include "mozilla/SegmentedVector.h" #include "ds/Bitmap.h" #include "gc/ArenaList.h" #include "gc/Barrier.h" #include "gc/FindSCCs.h" #include "gc/GCMarker.h" #include "gc/NurseryAwareHashMap.h" #include "gc/Statistics.h" #include "gc/ZoneAllocator.h" #include "js/GCHashTable.h" #include "vm/AtomsTable.h" #include "vm/JSFunction.h" namespace js { class RegExpZone; class WeakRefObject; namespace jit { class JitZone; } // namespace jit namespace gc { class ZoneList; using ZoneComponentFinder = ComponentFinder; struct UniqueIdGCPolicy { static bool needsSweep(Cell** cell, uint64_t* value); }; // Maps a Cell* to a unique, 64bit id. using UniqueIdMap = GCHashMap, SystemAllocPolicy, UniqueIdGCPolicy>; extern uint64_t NextCellUniqueId(JSRuntime* rt); template class ZoneAllCellIter; template class ZoneCellIter; // A vector of FinalizationRecord objects, or CCWs to them. using FinalizationRecordVector = GCVector; } // namespace gc // If two different nursery strings are wrapped into the same zone, and have // the same contents, then deduplication may make them duplicates. // `DuplicatesPossible` will allow this and map both wrappers to the same (now // tenured) source string. using StringWrapperMap = NurseryAwareHashMap, ZoneAllocPolicy, DuplicatesPossible>; class MOZ_NON_TEMPORARY_CLASS ExternalStringCache { static const size_t NumEntries = 4; mozilla::Array 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 { BaseScript* script; JSString* string; void set(BaseScript* scriptArg, JSString* stringArg) { script = scriptArg; string = stringArg; } }; static const size_t NumEntries = 2; mozilla::Array entries_; FunctionToStringCache(const FunctionToStringCache&) = delete; void operator=(const FunctionToStringCache&) = delete; public: FunctionToStringCache() { purge(); } void purge() { mozilla::PodArrayZero(entries_); } MOZ_ALWAYS_INLINE JSString* lookup(BaseScript* script) const; MOZ_ALWAYS_INLINE void put(BaseScript* script, JSString* string); }; // WeakRefHeapPtrVector is a GCVector of WeakRefObjects. class WeakRefHeapPtrVector : public GCVector { public: using GCVector::GCVector; // call in compacting, to update the target in each WeakRefObject. void sweep(js::HeapPtrObject& target); }; // WeakRefMap is a per-zone GCHashMap, which maps from the target of the JS // WeakRef to the list of JS WeakRefs. class WeakRefMap : public GCHashMap, ZoneAllocPolicy> { public: using GCHashMap::GCHashMap; using Base = GCHashMap, ZoneAllocPolicy>; void sweep(gc::StoreBuffer* sbToLock); }; } // namespace js namespace JS { // [SMDOC] GC Zones // // A zone is a collection of compartments. Every compartment belongs to exactly // one zone. In Firefox, there is roughly one zone per tab along with a system // zone for everything else. Zones mainly serve as boundaries for garbage // collection. Unlike compartments, they have no special security properties. // // Every GC thing belongs to exactly one zone. GC things from the same zone but // different compartments can share an arena (4k page). GC things from different // zones cannot be stored in the same arena. The garbage collector is capable of // collecting one zone at a time; it cannot collect at the granularity of // compartments. // // GC things are tied to zones and compartments as follows: // // - JSObjects belong to a compartment and cannot be shared between // compartments. If an object needs to point to a JSObject in a different // compartment, regardless of zone, it must go through a cross-compartment // wrapper. Each compartment keeps track of its outgoing wrappers in a table. // JSObjects find their compartment via their ObjectGroup. // // - JSStrings do not belong to any particular compartment, but they do belong // to a zone. Thus, two different compartments in the same zone can point to a // JSString. When a string needs to be wrapped, we copy it if it's in a // different zone and do nothing if it's in the same zone. Thus, transferring // strings within a zone is very efficient. // // - Shapes and base shapes belong to a zone and are shared between compartments // in that zone where possible. Accessor shapes store getter and setter // JSObjects which belong to a single compartment, so these shapes and all // their descendants can't be shared with other compartments. // // - Scripts are also compartment-local and cannot be shared. A script points to // its compartment. // // - ObjectGroup and JitCode objects belong to a compartment and cannot be // shared. There is no mechanism to obtain the compartment from a JitCode // object. // // A zone remains alive as long as any GC things in the zone are alive. A // compartment remains alive as long as any JSObjects, scripts, shapes, or base // shapes within it are alive. // // We always guarantee that a zone has at least one live compartment by refusing // to delete the last compartment in a live zone. class Zone : public js::ZoneAllocator, public js::gc::GraphNodeBase { private: enum class HelperThreadUse : uint32_t { None, Pending, Active }; mozilla::Atomic helperThreadUse_; // The helper thread context with exclusive access to this zone, if // usedByHelperThread(), or nullptr when on the main thread. js::UnprotectedData helperThreadOwnerContext_; public: js::gc::ArenaLists arenas; // Per-zone data for use by an embedder. js::ZoneData data; js::ZoneData tenuredBigInts; js::ZoneOrIonCompileData nurseryAllocatedStrings; // Number of marked/finalzied JSString/JSFatInlineString during major GC. js::ZoneOrGCTaskData markedStrings; js::ZoneOrGCTaskData finalizedStrings; js::ZoneData allocNurseryStrings; js::ZoneData allocNurseryBigInts; // When true, skip calling the metadata callback. We use this: // - to avoid invoking the callback recursively; // - to avoid observing lazy prototype setup (which confuses callbacks that // want to use the types being set up!); // - to avoid attaching allocation stacks to allocation stack nodes, which // is silly // And so on. js::ZoneData suppressAllocationMetadataBuilder; // Script side-tables. These used to be held by Realm, but are now placed // here in order to allow JSScript to access them during finalize (see bug // 1568245; this change in 1575350). The tables are initialized lazily by // JSScript. js::UniquePtr scriptCountsMap; js::UniquePtr scriptLCovMap; js::UniquePtr debugScriptMap; #ifdef MOZ_VTUNE js::UniquePtr scriptVTuneIdMap; #endif #ifdef JS_CACHEIR_SPEW js::UniquePtr scriptFinalWarmUpCountMap; #endif js::ZoneData previousGCStringStats; js::ZoneData stringStats; #ifdef DEBUG js::MainThreadData gcSweepGroupIndex; #endif private: // Side map for storing unique ids for cells, independent of address. js::ZoneOrGCTaskData uniqueIds_; // Number of allocations since the most recent minor GC for this thread. mozilla::Atomic tenuredAllocsSinceMinorGC_; // Live weakmaps in this zone. js::ZoneOrGCTaskData> gcWeakMapList_; // The set of compartments in this zone. using CompartmentVector = js::Vector; js::MainThreadOrGCTaskData compartments_; // All cross-zone string wrappers in the zone. js::MainThreadOrGCTaskData crossZoneStringWrappers_; // This zone's gray roots. using GrayRootVector = mozilla::SegmentedVector; js::ZoneOrGCTaskData gcGrayRoots_; // List of non-ephemeron weak containers to sweep during // beginSweepingSweepGroup. js::ZoneOrGCTaskData> weakCaches_; // Mapping from not yet marked keys to a vector of all values that the key // maps to in any live weak map. Separate tables for nursery and tenured // keys. js::ZoneOrGCTaskData gcWeakKeys_; js::ZoneOrGCTaskData gcNurseryWeakKeys_; // Keep track of all TypeDescr and related objects in this compartment. // This is used by the GC to trace them all first when compacting, since the // TypedObject trace hook may access these objects. // // There are no barriers here - the set contains only tenured objects so no // post-barrier is required, and these are weak references so no pre-barrier // is required. using TypeDescrObjectSet = js::GCHashSet, js::SystemAllocPolicy>; js::ZoneData> typeDescrObjects_; js::MainThreadData> regExps_; // Bitmap of atoms marked by this zone. js::ZoneOrGCTaskData markedAtoms_; // Set of atoms recently used by this Zone. Purged on GC. js::ZoneOrGCTaskData atomCache_; // Cache storing allocated external strings. Purged on GC. js::ZoneOrGCTaskData externalStringCache_; // Cache for Function.prototype.toString. Purged on GC. js::ZoneOrGCTaskData functionToStringCache_; // Shared Shape property tree. js::ZoneData propertyTree_; // Set of all unowned base shapes in the Zone. js::ZoneData baseShapes_; // Set of initial shapes in the Zone. For certain prototypes -- namely, // those of various builtin classes -- there are two entries: one for a // lookup via TaggedProto, and one for a lookup via JSProtoKey. See // InitialShapeProto. js::ZoneData initialShapes_; // List of shapes that may contain nursery pointers. using NurseryShapeVector = js::Vector; js::ZoneData nurseryShapes_; // The set of all finalization registries in this zone. using FinalizationRegistrySet = GCHashSet, js::ZoneAllocPolicy>; js::ZoneOrGCTaskData finalizationRegistries_; // A map from finalization registry targets to a list of finalization records // representing registries that the target is registered with and their // associated held values. using FinalizationRecordMap = GCHashMap, js::ZoneAllocPolicy>; js::ZoneOrGCTaskData finalizationRecordMap_; js::ZoneOrGCTaskData jitZone_; js::MainThreadData gcScheduled_; js::MainThreadData gcScheduledSaved_; js::MainThreadData gcPreserveCode_; js::ZoneData keepShapeCaches_; js::MainThreadData wasCollected_; // Allow zones to be linked into a list js::MainThreadOrGCTaskData listNext_; static Zone* const NotOnList; friend class js::gc::ZoneList; js::ZoneOrGCTaskData weakRefMap_; using KeptAliveSet = JS::GCHashSet, js::ZoneAllocPolicy>; friend class js::WeakRefObject; js::ZoneOrGCTaskData keptObjects; public: static JS::Zone* from(ZoneAllocator* zoneAlloc) { return static_cast(zoneAlloc); } explicit Zone(JSRuntime* rt, Kind kind = NormalZone); ~Zone(); MOZ_MUST_USE bool init(); void destroy(JSFreeOp* fop); bool ownedByCurrentHelperThread(); void setHelperThreadOwnerContext(JSContext* cx); // Whether this zone was created for use by a helper thread. bool createdForHelperThread() const { return helperThreadUse_ != HelperThreadUse::None; } // Whether this zone is currently in use by a helper thread. 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 ShouldDiscardJitScripts : bool { KeepJitScripts = false, DiscardJitScripts }; void discardJitCode( JSFreeOp* fop, ShouldDiscardBaselineCode discardBaselineCode = DiscardBaselineCode, ShouldDiscardJitScripts discardJitScripts = KeepJitScripts); void addSizeOfIncludingThis( mozilla::MallocSizeOf mallocSizeOf, JS::CodeSizes* code, size_t* regexpZone, size_t* jitZone, size_t* baselineStubsOptimized, size_t* uniqueIdMap, size_t* shapeCaches, size_t* atomsMarkBitmaps, size_t* compartmentObjects, size_t* crossCompartmentWrappersTables, size_t* compartmentsPrivateData, size_t* scriptCountsMapArg); // Iterate over all cells in the zone. See the definition of ZoneCellIter // in gc/GC-inl.h for the possible arguments and documentation. template js::gc::ZoneCellIter cellIter(Args&&... args) { return js::gc::ZoneCellIter(const_cast(this), std::forward(args)...); } // As above, but can return about-to-be-finalised things. template js::gc::ZoneAllCellIter cellIterUnsafe(Args&&... args) { return js::gc::ZoneAllCellIter(const_cast(this), std::forward(args)...); } 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_; } // Whether this zone can currently be collected. bool canCollect(); void changeGCState(GCState prev, 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 { // We only need to check needsIncrementalBarrier() for the pre-barrier // verifier. During marking isGCMarking() will always be true. return needsIncrementalBarrier() || isGCMarking(); } // Was this zone collected in the last GC. bool wasCollected() const { return wasCollected_; } void setWasCollected(bool v) { wasCollected_ = v; } // Get a number that is incremented whenever this zone is collected, and // possibly at other times too. 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_; } void prepareForCompacting(); void sweepAfterMinorGC(JSTracer* trc); void sweepUniqueIds(); void sweepWeakMaps(); void sweepCompartments(JSFreeOp* fop, bool keepAtleastOne, bool lastGC); js::gc::UniqueIdMap& uniqueIds() { return uniqueIds_.ref(); } void notifyObservingDebuggers(); void clearTables(); void addTenuredAllocsSinceMinorGC(uint32_t allocs) { tenuredAllocsSinceMinorGC_ += allocs; } uint32_t getAndResetTenuredAllocsSinceMinorGC() { return tenuredAllocsSinceMinorGC_.exchange(0); } mozilla::LinkedList& gcWeakMapList() { return gcWeakMapList_.ref(); } CompartmentVector& compartments() { return compartments_.ref(); } js::StringWrapperMap& crossZoneStringWrappers() { return crossZoneStringWrappers_.ref(); } const js::StringWrapperMap& crossZoneStringWrappers() const { return crossZoneStringWrappers_.ref(); } void dropStringWrappersOnGC(); void sweepAllCrossCompartmentWrappers(); static void fixupAllCrossCompartmentWrappersAfterMovingGC(JSTracer* trc); GrayRootVector& gcGrayRoots() { return gcGrayRoots_.ref(); } mozilla::LinkedList& weakCaches() { return weakCaches_.ref(); } void registerWeakCache(detail::WeakCacheBase* cachep) { weakCaches().insertBack(cachep); } void beforeClearDelegate(JSObject* wrapper, JSObject* delegate) { if (needsIncrementalBarrier()) { beforeClearDelegateInternal(wrapper, delegate); } } void afterAddDelegate(JSObject* wrapper) { if (needsIncrementalBarrier()) { afterAddDelegateInternal(wrapper); } } void beforeClearDelegateInternal(JSObject* wrapper, JSObject* delegate); void afterAddDelegateInternal(JSObject* wrapper); js::gc::WeakKeyTable& gcWeakKeys() { return gcWeakKeys_.ref(); } js::gc::WeakKeyTable& gcNurseryWeakKeys() { return gcNurseryWeakKeys_.ref(); } js::gc::WeakKeyTable& gcWeakKeys(const js::gc::Cell* cell) { return cell->isTenured() ? gcWeakKeys() : gcNurseryWeakKeys(); } // Perform all pending weakmap entry marking for this zone after // transitioning to weak marking mode. js::gc::IncrementalProgress enterWeakMarkingMode(js::GCMarker* marker, js::SliceBudget& budget); void checkWeakMarkingMode(); // A set of edges from this zone to other zones used during GC to calculate // sweep groups. NodeSet& gcSweepGroupEdges() { return gcGraphEdges; // Defined in GraphNodeBase base class. } bool hasSweepGroupEdgeTo(Zone* otherZone) const { return gcGraphEdges.has(otherZone); } MOZ_MUST_USE bool addSweepGroupEdgeTo(Zone* otherZone) { MOZ_ASSERT(otherZone->isGCMarking()); return gcSweepGroupEdges().put(otherZone); } void clearSweepGroupEdges() { gcSweepGroupEdges().clear(); } js::RegExpZone& regExps() { return *regExps_.ref(); } JS::WeakCache& typeDescrObjects() { return typeDescrObjects_.ref(); } bool addTypeDescrObject(JSContext* cx, HandleObject obj); js::SparseBitmap& markedAtoms() { return markedAtoms_.ref(); } js::AtomSet& atomCache() { return atomCache_.ref(); } void purgeAtomCache(); js::ExternalStringCache& externalStringCache() { return externalStringCache_.ref(); }; js::FunctionToStringCache& functionToStringCache() { return functionToStringCache_.ref(); } js::PropertyTree& propertyTree() { return propertyTree_.ref(); } js::BaseShapeSet& baseShapes() { return baseShapes_.ref(); } js::InitialShapeSet& initialShapes() { return initialShapes_.ref(); } NurseryShapeVector& nurseryShapes() { return nurseryShapes_.ref(); } void fixupInitialShapeTable(); void fixupAfterMovingGC(); void fixupScriptMapsAfterMovingGC(JSTracer* trc); static js::HashNumber UniqueIdToHash(uint64_t uid); // Creates a HashNumber based on getUniqueId. Returns false on OOM. MOZ_MUST_USE bool getHashCode(js::gc::Cell* cell, js::HashNumber* hashp); // Gets an existing UID in |uidp| if one exists. MOZ_MUST_USE bool maybeGetUniqueId(js::gc::Cell* cell, uint64_t* uidp); // Puts an existing UID in |uidp|, or creates a new UID for this Cell and // puts that into |uidp|. Returns false on OOM. 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); // Return true if this cell has a UID associated with it. MOZ_MUST_USE bool hasUniqueId(js::gc::Cell* cell); // Transfer an id from another cell. This must only be called on behalf of a // moving GC. This method is infallible. void transferUniqueId(js::gc::Cell* tgt, js::gc::Cell* src); // Remove any unique id associated with this Cell. void removeUniqueId(js::gc::Cell* cell); // When finished parsing off-thread, transfer any UIDs we created in the // off-thread zone into the target zone. void adoptUniqueIds(JS::Zone* source); bool keepShapeCaches() const { return keepShapeCaches_; } void setKeepShapeCaches(bool b) { keepShapeCaches_ = b; } // Delete an empty compartment after its contents have been merged. void deleteEmptyCompartment(JS::Compartment* comp); void clearRootsForShutdownGC(); void finishRoots(); void traceScriptTableRoots(JSTracer* trc); void clearScriptCounts(Realm* realm); void clearScriptLCov(Realm* realm); // Add the target of JS WeakRef to a kept-alive set maintained by GC. // See: https://tc39.es/proposal-weakrefs/#sec-keepduringjob bool keepDuringJob(HandleObject target); void traceKeptObjects(JSTracer* trc); // Clear the kept-alive set. // See: https://tc39.es/proposal-weakrefs/#sec-clear-kept-objects void clearKeptObjects(); #ifdef JSGC_HASH_TABLE_CHECKS void checkAllCrossCompartmentWrappersAfterMovingGC(); void checkStringWrappersAfterMovingGC(); void checkInitialShapesTableAfterMovingGC(); void checkBaseShapeTableAfterMovingGC(); // Assert that the UniqueId table has been redirected successfully. void checkUniqueIdTableAfterMovingGC(); void checkScriptMapsAfterMovingGC(); #endif #ifdef DEBUG // For testing purposes, return the index of the sweep group which this zone // was swept in in the last GC. unsigned lastSweepGroupIndex() { return gcSweepGroupIndex; } #endif private: js::jit::JitZone* createJitZone(JSContext* cx); bool isQueuedForBackgroundSweep() { return isOnList(); } void sweepWeakKeysAfterMinorGC(); FinalizationRegistrySet& finalizationRegistries() { return finalizationRegistries_.ref(); } FinalizationRecordMap& finalizationRecordMap() { return finalizationRecordMap_.ref(); } bool isOnList() const; Zone* nextZone() const; js::WeakRefMap& weakRefMap() { return weakRefMap_.ref(); } friend bool js::CurrentThreadCanAccessZone(Zone* zone); friend class js::gc::GCRuntime; }; } // namespace JS namespace js { namespace gc { const char* StateName(JS::Zone::GCState state); } // namespace gc } // namespace js #endif // gc_Zone_h