diff --git a/js/src/gc/Allocator.cpp b/js/src/gc/Allocator.cpp index e422dd6a99..2e1a4e7deb 100644 --- a/js/src/gc/Allocator.cpp +++ b/js/src/gc/Allocator.cpp @@ -295,8 +295,7 @@ GCRuntime::refillFreeListFromMainThread(JSContext* cx, AllocKind thingKind, size Zone *zone = cx->zone(); MOZ_ASSERT(!cx->runtime()->isHeapBusy(), "allocating while under GC"); - AutoMaybeStartBackgroundAllocation maybeStartBGAlloc; - return cx->arenas()->allocateFromArena(zone, thingKind, CheckThresholds, maybeStartBGAlloc); + return cx->arenas()->allocateFromArena(zone, thingKind, CheckThresholds); } /* static */ TenuredCell* @@ -307,8 +306,7 @@ GCRuntime::refillFreeListOffMainThread(ExclusiveContext* cx, AllocKind thingKind Zone* zone = cx->zone(); MOZ_ASSERT(!zone->wasGCStarted()); - AutoMaybeStartBackgroundAllocation maybeStartBGAlloc; - return cx->arenas()->allocateFromArena(zone, thingKind, CheckThresholds, maybeStartBGAlloc); + return cx->arenas()->allocateFromArena(zone, thingKind, CheckThresholds); } /* static */ TenuredCell* @@ -323,19 +321,16 @@ GCRuntime::refillFreeListInGC(Zone* zone, AllocKind thingKind) MOZ_ASSERT(rt->isHeapCollecting()); MOZ_ASSERT_IF(!rt->isHeapMinorCollecting(), !rt->gc.isBackgroundSweeping()); - AutoMaybeStartBackgroundAllocation maybeStartBackgroundAllocation; - return zone->arenas.allocateFromArena(zone, thingKind, DontCheckThresholds, - maybeStartBackgroundAllocation); + return zone->arenas.allocateFromArena(zone, thingKind, DontCheckThresholds); } TenuredCell* ArenaLists::allocateFromArena(JS::Zone* zone, AllocKind thingKind, - ShouldCheckThresholds checkThresholds, - AutoMaybeStartBackgroundAllocation& maybeStartBGAlloc) + ShouldCheckThresholds checkThresholds) { JSRuntime* rt = zone->runtimeFromAnyThread(); - mozilla::Maybe maybeLock; + mozilla::Maybe maybeLock; // See if we can proceed without taking the GC lock. if (backgroundFinalizeState[thingKind] != BFS_DONE) @@ -355,7 +350,7 @@ ArenaLists::allocateFromArena(JS::Zone* zone, AllocKind thingKind, if (maybeLock.isNothing()) maybeLock.emplace(rt); - Chunk* chunk = rt->gc.pickChunk(maybeLock.ref(), maybeStartBGAlloc); + Chunk* chunk = rt->gc.pickChunk(maybeLock.ref()); if (!chunk) return nullptr; @@ -507,8 +502,7 @@ Chunk::findDecommittedArenaOffset() // /////////// System -> Chunk Allocator ///////////////////////////////////// Chunk* -GCRuntime::getOrAllocChunk(const AutoLockGC& lock, - AutoMaybeStartBackgroundAllocation& maybeStartBackgroundAllocation) +GCRuntime::getOrAllocChunk(AutoLockGCBgAlloc& lock) { Chunk* chunk = emptyChunks(lock).pop(); if (!chunk) { @@ -519,7 +513,7 @@ GCRuntime::getOrAllocChunk(const AutoLockGC& lock, } if (wantBackgroundAllocation(lock)) - maybeStartBackgroundAllocation.tryToStartBackgroundAllocation(rt->gc); + lock.tryToStartBackgroundAllocation(); return chunk; } @@ -531,13 +525,12 @@ GCRuntime::recycleChunk(Chunk* chunk, const AutoLockGC& lock) } Chunk* -GCRuntime::pickChunk(const AutoLockGC& lock, - AutoMaybeStartBackgroundAllocation& maybeStartBackgroundAllocation) +GCRuntime::pickChunk(AutoLockGCBgAlloc& lock) { if (availableChunks(lock).count()) return availableChunks(lock).head(); - Chunk* chunk = getOrAllocChunk(lock, maybeStartBackgroundAllocation); + Chunk* chunk = getOrAllocChunk(lock); if (!chunk) return nullptr; diff --git a/js/src/gc/Barrier.cpp b/js/src/gc/Barrier.cpp index 81b7c72e0f..15c5008759 100644 --- a/js/src/gc/Barrier.cpp +++ b/js/src/gc/Barrier.cpp @@ -32,15 +32,8 @@ RuntimeFromMainThreadIsHeapMajorCollecting(JS::shadow::Zone* shadowZone) bool IsMarkedBlack(NativeObject* obj) { - // Note: we assume conservatively that Nursery things will be live. - if (!obj->isTenured()) - return true; - - gc::TenuredCell& tenured = obj->asTenured(); - if (tenured.isMarkedAny() || tenured.arena()->allocatedDuringIncremental) - return true; - - return false; + return obj->isMarkedBlack() || + (obj->isTenured() && obj->asTenured().arena()->allocatedDuringIncremental); } bool diff --git a/js/src/gc/Barrier.h b/js/src/gc/Barrier.h index 345131a296..b0c4917af7 100644 --- a/js/src/gc/Barrier.h +++ b/js/src/gc/Barrier.h @@ -1,4 +1,5 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: * 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/. */ @@ -285,7 +286,7 @@ struct InternalBarrierMethods DispatchTyped(PreBarrierFunctor(), v); } - static void postBarrier(Value* vp, const Value& prev, const Value& next) { + static MOZ_ALWAYS_INLINE void postBarrier(Value* vp, const Value& prev, const Value& next) { MOZ_ASSERT(!CurrentThreadIsIonCompiling()); MOZ_ASSERT(vp); @@ -372,7 +373,7 @@ class WriteBarrieredBase : public BarrieredBase, protected: void pre() { InternalBarrierMethods::preBarrier(this->value); } - void post(const T& prev, const T& next) { + MOZ_ALWAYS_INLINE void post(const T& prev, const T& next) { InternalBarrierMethods::postBarrier(&this->value, prev, next); } }; diff --git a/js/src/gc/GCRuntime.h b/js/src/gc/GCRuntime.h index a062f8b1a9..473efb94fb 100644 --- a/js/src/gc/GCRuntime.h +++ b/js/src/gc/GCRuntime.h @@ -23,6 +23,7 @@ namespace js { class AutoLockGC; +class AutoLockGCBgAlloc; class AutoLockHelperThreadState; class VerifyPreTracer; @@ -31,7 +32,9 @@ namespace gc { typedef Vector ZoneVector; using BlackGrayEdgeVector = Vector; -class AutoMaybeStartBackgroundAllocation; +class AutoCallGCCallbacks; +class AutoRunParallelTask; +class AutoTraceSession; class MarkingValidator; class AutoTraceSession; struct MovingTracer; @@ -119,28 +122,29 @@ class BackgroundDecommitTask : public GCParallelTaskHelper gcMaxBytes_; /* - * JSGC_MAX_MALLOC_BYTES + * JSGC_MAX_MALLOC_BYTES * * Initial malloc bytes threshold. */ UnprotectedData maxMallocBytes_; /* + * JSGC_MAX_NURSERY_BYTES + * * Maximum nursery size for each zone group. - * Initially DefaultNurseryBytes and can be set by - * javascript.options.mem.nursery.max_kb */ ActiveThreadData gcMaxNurseryBytes_; /* + * JSGC_ALLOCATION_THRESHOLD + * * The base value used to compute zone->threshold.gcTriggerBytes(). When * usage.gcBytes() surpasses threshold.gcTriggerBytes() for a zone, the * zone may be scheduled for a GC, depending on the exact circumstances. @@ -153,6 +157,7 @@ class GCSchedulingTunables * Fraction of threshold.gcBytes() which triggers an incremental GC. */ UnprotectedData allocThresholdFactor_; + /* * JSGC_ALLOCATION_THRESHOLD_FACTOR_AVOID_INTERRUPT * @@ -163,22 +168,33 @@ class GCSchedulingTunables /* * Number of bytes to allocate between incremental slices in GCs triggered * by the zone allocation threshold. + * + * This value does not have a JSGCParamKey parameter yet. */ size_t zoneAllocDelayBytes_; /* + * JSGC_DYNAMIC_HEAP_GROWTH + * * Totally disables |highFrequencyGC|, the HeapGrowthFactor, and other * tunables that make GC non-deterministic. */ bool dynamicHeapGrowthEnabled_; /* + * JSGC_HIGH_FREQUENCY_TIME_LIMIT + * * We enter high-frequency mode if we GC a twice within this many * microseconds. This value is stored directly in microseconds. */ uint64_t highFrequencyThresholdUsec_; /* + * JSGC_HIGH_FREQUENCY_LOW_LIMIT + * JSGC_HIGH_FREQUENCY_HIGH_LIMIT + * JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MAX + * JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MIN + * * When in the |highFrequencyGC| mode, these parameterize the per-zone * "HeapGrowthFactor" computation. */ @@ -188,22 +204,31 @@ class GCSchedulingTunables double highFrequencyHeapGrowthMin_; /* + * JSGC_LOW_FREQUENCY_HEAP_GROWTH + * * When not in |highFrequencyGC| mode, this is the global (stored per-zone) * "HeapGrowthFactor". */ double lowFrequencyHeapGrowth_; /* + * JSGC_DYNAMIC_MARK_SLICE + * * Doubles the length of IGC slices when in the |highFrequencyGC| mode. */ bool dynamicMarkSliceEnabled_; /* + * JSGC_REFRESH_FRAME_SLICES_ENABLED + * * Controls whether painting can trigger IGC slices. */ bool refreshFrameSlicesEnabled_; /* + * JSGC_MIN_EMPTY_CHUNK_COUNT + * JSGC_MAX_EMPTY_CHUNK_COUNT + * * Controls the number of empty chunks reserved for future allocation. */ uint32_t minEmptyChunkCount_; @@ -213,7 +238,7 @@ class GCSchedulingTunables GCSchedulingTunables(); size_t gcMaxBytes() const { return gcMaxBytes_; } - size_t maxMallocBytes() const { return maxMallocBytes_; } + size_t maxMallocBytes() const { return maxMallocBytes_; } size_t gcMaxNurseryBytes() const { return gcMaxNurseryBytes_; } size_t gcZoneAllocThresholdBase() const { return gcZoneAllocThresholdBase_; } float allocThresholdFactor() const { return allocThresholdFactor_; } @@ -664,8 +689,8 @@ class MemoryCounter return NonIncrementalTrigger; } - - bool shouldResetIncrementalGC(const GCSchedulingTunables& tunables) const { + + bool shouldResetIncrementalGC(const GCSchedulingTunables& tunables) const { return bytes_ > maxBytes_ * tunables.allocThresholdFactorAvoidInterrupt(); } @@ -674,7 +699,7 @@ class MemoryCounter void updateOnGCStart(); void updateOnGCEnd(const GCSchedulingTunables& tunables, const AutoLockGC& lock); - private: + private: void reset(); }; @@ -691,7 +716,7 @@ class GCRuntime void setMarkStackLimit(size_t limit, AutoLockGC& lock); [[nodiscard]] bool setParameter(JSGCParamKey key, uint32_t value, AutoLockGC& lock); - void resetParameter(JSGCParamKey key, AutoLockGC& lock); + void resetParameter(JSGCParamKey key, AutoLockGC& lock); uint32_t getParameter(JSGCParamKey key, const AutoLockGC& lock); [[nodiscard]] bool triggerGC(JS::gcreason::Reason reason); @@ -847,21 +872,22 @@ class GCRuntime int32_t getMallocBytes() const { return mallocCounter.bytes(); } size_t maxMallocBytesAllocated() const { return mallocCounter.maxBytes(); } void setMaxMallocBytes(size_t value, const AutoLockGC& lock); - - bool updateMallocCounter(size_t nbytes) { + + bool updateMallocCounter(size_t nbytes) { mallocCounter.update(nbytes); TriggerKind trigger = mallocCounter.shouldTriggerGC(tunables); if (MOZ_LIKELY(trigger == NoTrigger) || trigger <= mallocCounter.triggered()) return false; - if (!triggerGC(JS::gcreason::TOO_MUCH_MALLOC)) - return false; + + if (!triggerGC(JS::gcreason::TOO_MUCH_MALLOC)) + return false; // Even though this method may be called off the main thread it is safe // to access mallocCounter here since triggerGC() will return false in // that case. stats().recordTrigger(mallocCounter.bytes(), mallocCounter.maxBytes()); - mallocCounter.recordTrigger(trigger); + mallocCounter.recordTrigger(trigger); return true; } @@ -889,7 +915,7 @@ class GCRuntime void setFullCompartmentChecks(bool enable); JS::Zone* getCurrentSweepGroup() { return currentSweepGroup; } - void setFoundBlackGrayEdges(TenuredCell& target) { + void setFoundBlackGrayEdges(TenuredCell& target) { AutoEnterOOMUnsafeRegion oomUnsafe; if (!foundBlackGrayEdges.ref().append(&target)) oomUnsafe.crash("OOM|small: failed to insert into foundBlackGrayEdges"); @@ -934,12 +960,12 @@ class GCRuntime const ChunkPool& availableChunks(const AutoLockGC& lock) const { return availableChunks_; } const ChunkPool& emptyChunks(const AutoLockGC& lock) const { return emptyChunks_; } typedef ChainedIter NonEmptyChunksIter; - NonEmptyChunksIter allNonEmptyChunks() { - return NonEmptyChunksIter(ChunkPool::Iter(availableChunks_.ref()), ChunkPool::Iter(fullChunks_.ref())); + NonEmptyChunksIter allNonEmptyChunks(const AutoLockGC& lock) { + return NonEmptyChunksIter(ChunkPool::Iter(availableChunks(lock)), + ChunkPool::Iter(fullChunks(lock))); } - Chunk* getOrAllocChunk(const AutoLockGC& lock, - AutoMaybeStartBackgroundAllocation& maybeStartBGAlloc); + Chunk* getOrAllocChunk(AutoLockGCBgAlloc& lock); void recycleChunk(Chunk* chunk, const AutoLockGC& lock); // Free certain LifoAlloc blocks when it is safe to do so. @@ -993,10 +1019,11 @@ class GCRuntime // For ArenaLists::allocateFromArena() friend class ArenaLists; - Chunk* pickChunk(const AutoLockGC& lock, - AutoMaybeStartBackgroundAllocation& maybeStartBGAlloc); + Chunk* pickChunk(AutoLockGCBgAlloc& lock); Arena* allocateArena(Chunk* chunk, Zone* zone, AllocKind kind, ShouldCheckThresholds checkThresholds, const AutoLockGC& lock); + + void arenaAllocatedDuringGC(JS::Zone* zone, Arena* arena); // Allocator internals @@ -1019,7 +1046,6 @@ class GCRuntime void prepareToFreeChunk(ChunkInfo& info); friend class BackgroundAllocTask; - friend class AutoMaybeStartBackgroundAllocation; bool wantBackgroundAllocation(const AutoLockGC& lock) const; void startBackgroundAllocTaskIfIdle(); @@ -1045,9 +1071,14 @@ class GCRuntime void incrementalCollectSlice(SliceBudget& budget, JS::gcreason::Reason reason, AutoLockForExclusiveAccess& lock); + friend class AutoCallGCCallbacks; + void maybeCallBeginCallback(); + void maybeCallEndCallback(); + + void pushZealSelectedObjects(); void purgeRuntime(AutoLockForExclusiveAccess& lock); [[nodiscard]] bool beginMarkPhase(JS::gcreason::Reason reason, AutoLockForExclusiveAccess& lock); - bool prepareZonesForCollection(JS::gcreason::Reason reason, bool* isFullOut, + bool prepareZonesForCollection(JS::gcreason::Reason reason, bool* isFullOut, AutoLockForExclusiveAccess& lock); bool shouldPreserveJITCode(JSCompartment* comp, int64_t currentTime, JS::gcreason::Reason reason, bool canAllocateMoreCode); @@ -1163,11 +1194,11 @@ class GCRuntime // to the fullChunks pool. During a GC, if all arenas are free, the chunk // is moved back to the emptyChunks pool and scheduled for eventual // release. - UnprotectedData availableChunks_; + GCLockData availableChunks_; // When all arenas in a chunk are used, it is moved to the fullChunks pool // so as to reduce the cost of operations on the available lists. - UnprotectedData fullChunks_; + GCLockData fullChunks_; RootedValueMap rootsHash; @@ -1186,7 +1217,12 @@ class GCRuntime bool chunkAllocationSinceLastGC; int64_t lastGCTime; - JSGCMode mode; + /* + * JSGC_MODE + * prefs: javascript.options.mem.gc_per_zone and + * javascript.options.mem.gc_incremental. + */ + ActiveThreadData mode; mozilla::Atomic numActiveZoneIters; @@ -1274,7 +1310,6 @@ class GCRuntime ActiveThreadData useZeal; #endif - /* Indicates that the last incremental slice exhausted the mark stack. */ ActiveThreadData lastMarkSlice; @@ -1343,8 +1378,13 @@ class GCRuntime */ bool interFrameGC; - /* Default budget for incremental GC slice. See js/SliceBudget.h. */ - int64_t defaultTimeBudget_; + /* + * Default budget for incremental GC slice. See js/SliceBudget.h. + * + * JSGC_SLICE_TIME_BUDGET + * pref: javascript.options.mem.gc_incremental_slice_ms, + */ + ActiveThreadData defaultTimeBudget_; /* * We disable incremental GC if we encounter a Class with a trace hook @@ -1359,6 +1399,9 @@ class GCRuntime /* * Whether compacting GC can is enabled globally. + * + * JSGC_COMPACTING_ENABLED + * pref: javascript.options.mem.gc_compacting */ bool compactingEnabled; @@ -1389,6 +1432,8 @@ class GCRuntime bool fullCompartmentChecks; + ActiveThreadData gcBeginCallbackDepth; + Callback gcCallback; Callback gcDoCycleCollectionCallback; Callback tenuredCallback; @@ -1427,6 +1472,7 @@ class GCRuntime /* Synchronize GC heap access between main thread and GCHelperState. */ friend class js::AutoLockGC; + friend class js::AutoLockGCBgAlloc; js::Mutex lock; BackgroundAllocTask allocTask; @@ -1460,29 +1506,51 @@ class MOZ_RAII AutoEnterIteration { } }; -// After pulling a Chunk out of the empty chunks pool, we want to run the -// background allocator to refill it. The code that takes Chunks does so under -// the GC lock. We need to start the background allocation under the helper -// threads lock. To avoid lock inversion we have to delay the start until after -// we are outside the GC lock. This class handles that delay automatically. -class MOZ_RAII AutoMaybeStartBackgroundAllocation +#ifdef JS_GC_ZEAL + +inline bool +GCRuntime::hasZealMode(ZealMode mode) { - GCRuntime* gc; + static_assert(size_t(ZealMode::Limit) < sizeof(zealModeBits) * 8, + "Zeal modes must fit in zealModeBits"); + return zealModeBits & (1 << uint32_t(mode)); +} - public: - AutoMaybeStartBackgroundAllocation() - : gc(nullptr) - {} +inline void +GCRuntime::clearZealMode(ZealMode mode) +{ + zealModeBits &= ~(1 << uint32_t(mode)); + MOZ_ASSERT(!hasZealMode(mode)); +} - void tryToStartBackgroundAllocation(GCRuntime& gc) { - this->gc = &gc; +inline bool +GCRuntime::upcomingZealousGC() { + return nextScheduled == 1; +} + +inline bool +GCRuntime::needZealousGC() { + if (nextScheduled > 0 && --nextScheduled == 0) { + if (hasZealMode(ZealMode::Alloc) || + hasZealMode(ZealMode::GenerationalGC) || + hasZealMode(ZealMode::IncrementalRootsThenFinish) || + hasZealMode(ZealMode::IncrementalMarkAllThenFinish) || + hasZealMode(ZealMode::IncrementalMultipleSlices) || + hasZealMode(ZealMode::Compact) || + hasZealMode(ZealMode::IncrementalSweepThenFinish)) + { + nextScheduled = zealFrequency; + } + return true; } - - ~AutoMaybeStartBackgroundAllocation() { - if (gc) - gc->startBackgroundAllocTaskIfIdle(); - } -}; + return false; +} +#else +inline bool GCRuntime::hasZealMode(ZealMode mode) { return false; } +inline void GCRuntime::clearZealMode(ZealMode mode) { } +inline bool GCRuntime::upcomingZealousGC() { return false; } +inline bool GCRuntime::needZealousGC() { return false; } +#endif } /* namespace gc */ diff --git a/js/src/gc/GenerateStatsPhases.py b/js/src/gc/GenerateStatsPhases.py index 53a465569c..ece873dc05 100644 --- a/js/src/gc/GenerateStatsPhases.py +++ b/js/src/gc/GenerateStatsPhases.py @@ -140,7 +140,6 @@ PhaseKindGraphRoots = [ PhaseKind("SWEEP_SCOPE", "Sweep Scope", 59), PhaseKind("SWEEP_REGEXP_SHARED", "Sweep RegExpShared", 61), PhaseKind("SWEEP_SHAPE", "Sweep Shape", 36), - PhaseKind("SWEEP_JITCODE", "Sweep JIT code", 37), PhaseKind("FINALIZE_END", "Finalize End Callback", 38), PhaseKind("DESTROY", "Deallocate", 39), JoinParallelTasksPhaseKind diff --git a/js/src/gc/Heap.h b/js/src/gc/Heap.h index d77d49dcf6..8aa508c978 100644 --- a/js/src/gc/Heap.h +++ b/js/src/gc/Heap.h @@ -31,6 +31,8 @@ #include "js/TracingAPI.h" #include "js/TraceKind.h" +#include "vm/Printer.h" + struct JSRuntime; namespace JS { @@ -42,6 +44,7 @@ struct Runtime; namespace js { class AutoLockGC; +class AutoLockGCBgAlloc; class FreeOp; extern bool @@ -59,6 +62,7 @@ CurrentThreadIsIonCompiling(); extern bool UnmarkGrayCellRecursively(gc::Cell* cell, JS::TraceKind kind); + extern void TraceManuallyBarrieredGenericPointerEdge(JSTracer* trc, gc::Cell** thingp, const char* name); @@ -305,7 +309,6 @@ struct Cell return static_cast(this); } - #ifdef DEBUG inline bool isAligned() const; void dump(FILE* fp) const; @@ -1102,6 +1105,9 @@ static_assert(js::gc::ChunkRuntimeOffset == offsetof(Chunk, trailer) + static_assert(js::gc::ChunkLocationOffset == offsetof(Chunk, trailer) + offsetof(ChunkTrailer, location), "The hardcoded API location offset must match the actual offset."); +static_assert(js::gc::ChunkStoreBufferOffset == offsetof(Chunk, trailer) + + offsetof(ChunkTrailer, storeBuffer), + "The hardcoded API storeBuffer offset must match the actual offset."); /* * Tracks the used sizes for owned heap data and automatically maintains the diff --git a/js/src/gc/Iteration.cpp b/js/src/gc/Iteration.cpp index 3d5be9b9f3..c011e184b0 100644 --- a/js/src/gc/Iteration.cpp +++ b/js/src/gc/Iteration.cpp @@ -75,8 +75,9 @@ void js::IterateChunks(JSContext* cx, void* data, IterateChunkCallback chunkCallback) { AutoPrepareForTracing prep(cx, SkipAtoms); + AutoLockGC lock(cx->runtime()); - for (auto chunk = cx->runtime()->gc.allNonEmptyChunks(); !chunk.done(); chunk.next()) + for (auto chunk = cx->runtime()->gc.allNonEmptyChunks(lock); !chunk.done(); chunk.next()) chunkCallback(cx->runtime(), data, chunk); } diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index 2b35533b21..a7670e59ac 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -39,6 +39,7 @@ #include "jsobjinlines.h" #include "gc/Nursery-inl.h" +#include "vm/NativeObject-inl.h" #include "vm/String-inl.h" #include "vm/UnboxedObject-inl.h" @@ -1647,6 +1648,18 @@ ObjectDenseElementsMayBeMarkable(NativeObject* nobj) return mayBeMarkable; } +static inline void +CheckForCompartmentMismatch(JSObject* obj, JSObject* obj2) +{ +#ifdef DEBUG + if (MOZ_UNLIKELY(obj->compartment() != obj2->compartment())) { + fprintf(stderr, "Compartment mismatch in pointer from %s object slot to %s object\n", + obj->getClass()->name, obj2->getClass()->name); + MOZ_CRASH("Compartment mismatch"); + } +#endif +} + inline void GCMarker::processMarkStackTop(SliceBudget& budget) { @@ -1727,7 +1740,7 @@ GCMarker::processMarkStackTop(SliceBudget& budget) traverseEdge(obj, v.toString()); } else if (v.isObject()) { JSObject* obj2 = &v.toObject(); - MOZ_ASSERT(obj->compartment() == obj2->compartment()); + CheckForCompartmentMismatch(obj, obj2); if (mark(obj2)) { // Save the rest of this value array for later and start scanning obj2's children. pushValueArray(obj, vp, end); @@ -2148,7 +2161,7 @@ GCMarker::enterWeakMarkingMode() if (weakMapAction() == ExpandWeakMaps) { tag_ = TracerKindTag::WeakMarking; - for (GCSweepGroupIter zone(runtime()); !zone.done(); zone.next()) { + for (SweepGroupZonesIter zone(runtime()); !zone.done(); zone.next()) { for (WeakMapBase* m : zone->gcWeakMapList()) { if (m->marked) (void) m->traceEntries(this); @@ -2288,8 +2301,18 @@ TenuringTracer::traverse(JSObject** objp) // We only ever visit the internals of objects after moving them to tenured. MOZ_ASSERT(!nursery().isInside(objp)); - if (IsInsideNursery(*objp) && !nursery().getForwardedPointer(objp)) - *objp = moveToTenured(*objp); + JSObject* obj = *objp; + if (!IsInsideNursery(obj) || nursery().getForwardedPointer(objp)) + return; + + // Take a fast path for tenuring a plain object which is by far the most + // common case. + if (obj->is()) { + *objp = movePlainObjectToTenured(&obj->as()); + return; + } + + *objp = moveToTenuredSlow(obj); } template @@ -2336,8 +2359,7 @@ void js::gc::StoreBuffer::SlotsEdge::trace(TenuringTracer& mover) const { NativeObject* obj = object(); - if(!IsCellPointerValid(obj)) - return; + MOZ_ASSERT(IsCellPointerValid(obj)); // Beware JSObject::swap exchanging a native object for a non-native one. if (!obj->isNative()) @@ -2407,8 +2429,7 @@ js::gc::StoreBuffer::traceWholeCells(TenuringTracer& mover) { for (ArenaCellSet* cells = bufferWholeCell; cells; cells = cells->next) { Arena* arena = cells->arena; - if(!IsCellPointerValid(arena)) - continue; + MOZ_ASSERT(IsCellPointerValid(arena)); MOZ_ASSERT(arena->bufferedCells == cells); arena->bufferedCells = &ArenaCellSet::Empty; @@ -2439,6 +2460,7 @@ js::gc::StoreBuffer::CellPtrEdge::trace(TenuringTracer& mover) const return; // XXX: We should check if the cell pointer is valid here too + MOZ_ASSERT(IsCellPointerValid(*edge)); MOZ_ASSERT((*edge)->getTraceKind() == JS::TraceKind::Object); mover.traverse(reinterpret_cast(edge)); } @@ -2450,59 +2472,6 @@ js::gc::StoreBuffer::ValueEdge::trace(TenuringTracer& mover) const mover.traverse(edge); } -/* Insert the given relocation entry into the list of things to visit. */ -void -js::TenuringTracer::insertIntoFixupList(RelocationOverlay* entry) { - *tail = entry; - tail = &entry->nextRef(); - *tail = nullptr; -} - -JSObject* -js::TenuringTracer::moveToTenured(JSObject* src) -{ - MOZ_ASSERT(IsInsideNursery(src)); - MOZ_ASSERT(!src->zone()->usedByHelperThread()); - - AllocKind dstKind = src->allocKindForTenure(nursery()); - Zone* zone = src->zone(); - - TenuredCell* t = zone->arenas.allocateFromFreeList(dstKind, Arena::thingSize(dstKind)); - if (!t) { - AutoEnterOOMUnsafeRegion oomUnsafe; - t = runtime()->gc.refillFreeListInGC(zone, dstKind); - if (!t) - oomUnsafe.crash(ChunkSize, "Failed to allocate object while tenuring."); - } - JSObject* dst = reinterpret_cast(t); - tenuredSize += moveObjectToTenured(dst, src, dstKind); - - RelocationOverlay* overlay = RelocationOverlay::fromCell(src); - overlay->forwardTo(dst); - insertIntoFixupList(overlay); - - TracePromoteToTenured(src, dst); - MemProfiler::MoveNurseryToTenured(src, dst); - return dst; -} - -void -js::Nursery::collectToFixedPoint(TenuringTracer& mover, TenureCountCache& tenureCounts) -{ - for (RelocationOverlay* p = mover.head; p; p = p->next()) { - JSObject* obj = static_cast(p->forwardingAddress()); - mover.traceObject(obj); - - TenureCount& entry = tenureCounts.findEntry(obj->groupRaw()); - if (entry.group == obj->groupRaw()) { - entry.count++; - } else if (!entry.group) { - entry.group = obj->groupRaw(); - entry.count = 1; - } - } -} - struct TenuringFunctor { template @@ -2562,11 +2531,39 @@ OffsetToChunkEnd(void* p) } #endif -size_t -js::TenuringTracer::moveObjectToTenured(JSObject* dst, JSObject* src, AllocKind dstKind) +/* Insert the given relocation entry into the list of things to visit. */ +inline void +js::TenuringTracer::insertIntoFixupList(RelocationOverlay* entry) { + *tail = entry; + tail = &entry->nextRef(); + *tail = nullptr; +} + +template +inline T* +js::TenuringTracer::allocTenured(Zone* zone, AllocKind kind) { + TenuredCell* t = zone->arenas.allocateFromFreeList(kind, Arena::thingSize(kind)); + if (!t) { + AutoEnterOOMUnsafeRegion oomUnsafe; + t = runtime()->gc.refillFreeListInGC(zone, kind); + if (!t) + oomUnsafe.crash(ChunkSize, "Failed to allocate object while tenuring."); + } + return static_cast(static_cast(t)); +} + +JSObject* +js::TenuringTracer::moveToTenuredSlow(JSObject* src) { + MOZ_ASSERT(IsInsideNursery(src)); + MOZ_ASSERT(!src->zone()->usedByHelperThread()); + MOZ_ASSERT(!src->is()); + + AllocKind dstKind = src->allocKindForTenure(nursery()); + auto dst = allocTenured(src->zone(), dstKind); + size_t srcSize = Arena::thingSize(dstKind); - size_t tenuredSize = srcSize; + size_t dstSize = srcSize; /* * Arrays do not necessarily have the same AllocKind between src and dst. @@ -2578,7 +2575,7 @@ js::TenuringTracer::moveObjectToTenured(JSObject* dst, JSObject* src, AllocKind * even if they are inlined. */ if (src->is()) { - tenuredSize = srcSize = sizeof(NativeObject); + dstSize = srcSize = sizeof(NativeObject); } else if (src->is()) { TypedArrayObject* tarray = &src->as(); // Typed arrays with inline data do not necessarily have the same @@ -2596,13 +2593,12 @@ js::TenuringTracer::moveObjectToTenured(JSObject* dst, JSObject* src, AllocKind } } + tenuredSize += dstSize; + // Copy the Cell contents. MOZ_ASSERT(OffsetToChunkEnd(src) >= ptrdiff_t(srcSize)); js_memcpy(dst, src, srcSize); - // Move any hash code attached to the object. - src->zone()->transferUniqueId(dst, src); - // Move the slots and elements, if we need to. if (src->isNative()) { NativeObject* ndst = &dst->as(); @@ -2610,34 +2606,59 @@ js::TenuringTracer::moveObjectToTenured(JSObject* dst, JSObject* src, AllocKind tenuredSize += moveSlotsToTenured(ndst, nsrc, dstKind); tenuredSize += moveElementsToTenured(ndst, nsrc, dstKind); - // The shape's list head may point into the old object. This can only - // happen for dictionaries, which are native objects. - if (&nsrc->shape_ == ndst->shape_->listp) { - MOZ_ASSERT(nsrc->shape_->inDictionary()); - ndst->shape_->listp = &ndst->shape_; - } + // There is a pointer into a dictionary mode object from the head of its + // shape list. This is updated in Nursery::sweepDictionaryModeObjects(). } - if (src->is()) { - InlineTypedObject::objectMovedDuringMinorGC(this, dst, src); - } else if (src->is()) { - tenuredSize += TypedArrayObject::objectMovedDuringMinorGC(this, dst, src, dstKind); - } else if (src->is()) { - tenuredSize += UnboxedArrayObject::objectMovedDuringMinorGC(this, dst, src, dstKind); - } else if (src->is()) { - tenuredSize += ArgumentsObject::objectMovedDuringMinorGC(this, dst, src); - } else if (src->is()) { - tenuredSize += ProxyObject::objectMovedDuringMinorGC(this, dst, src); - } else if (JSObjectMovedOp op = dst->getClass()->extObjectMovedOp()) { - op(dst, src); - } else if (src->getClass()->hasFinalize()) { - // Such objects need to be handled specially above to ensure any - // additional nursery buffers they hold are moved. - MOZ_RELEASE_ASSERT(CanNurseryAllocateFinalizedClass(src->getClass())); - MOZ_CRASH("Unhandled JSCLASS_SKIP_NURSERY_FINALIZE Class"); + JSObjectMovedOp op = dst->getClass()->extObjectMovedOp(); + MOZ_ASSERT_IF(src->is(), op == proxy_ObjectMoved); + if (op) { + // Tell the hazard analysis that the object moved hook can't GC. + JS::AutoSuppressGCAnalysis nogc; + tenuredSize += op(dst, src); + } else { + MOZ_ASSERT_IF(src->getClass()->hasFinalize(), + CanNurseryAllocateFinalizedClass(src->getClass())); } - return tenuredSize; + RelocationOverlay* overlay = RelocationOverlay::fromCell(src); + overlay->forwardTo(dst); + insertIntoFixupList(overlay); + + TracePromoteToTenured(src, dst); + return dst; +} + +inline JSObject* +js::TenuringTracer::movePlainObjectToTenured(PlainObject* src) +{ + // Fast path version of moveToTenuredSlow() for specialized for PlainObject. + + MOZ_ASSERT(IsInsideNursery(src)); + MOZ_ASSERT(!src->zone()->usedByHelperThread()); + + AllocKind dstKind = src->allocKindForTenure(); + auto dst = allocTenured(src->zone(), dstKind); + + size_t srcSize = Arena::thingSize(dstKind); + tenuredSize += srcSize; + + // Copy the Cell contents. + MOZ_ASSERT(OffsetToChunkEnd(src) >= ptrdiff_t(srcSize)); + js_memcpy(dst, src, srcSize); + + // Move the slots and elements. + tenuredSize += moveSlotsToTenured(dst, src, dstKind); + tenuredSize += moveElementsToTenured(dst, src, dstKind); + + MOZ_ASSERT(!dst->getClass()->extObjectMovedOp()); + + RelocationOverlay* overlay = RelocationOverlay::fromCell(src); + overlay->forwardTo(dst); + insertIntoFixupList(overlay); + + TracePromoteToTenured(src, dst); + return dst; } size_t @@ -2712,6 +2733,23 @@ js::TenuringTracer::moveElementsToTenured(NativeObject* dst, NativeObject* src, return nslots * sizeof(HeapSlot); } +void +js::Nursery::collectToFixedPoint(TenuringTracer& mover, TenureCountCache& tenureCounts) +{ + for (RelocationOverlay* p = mover.head; p; p = p->next()) { + JSObject* obj = static_cast(p->forwardingAddress()); + mover.traceObject(obj); + + TenureCount& entry = tenureCounts.findEntry(obj->groupRaw()); + if (entry.group == obj->groupRaw()) { + entry.count++; + } else if (!entry.group) { + entry.group = obj->groupRaw(); + entry.count = 1; + } + } +} + /*** IsMarked / IsAboutToBeFinalized **************************************************************/ @@ -2970,8 +3008,8 @@ struct UnmarkGrayTracer : public JS::CallbackTracer * There is an additional complication for certain kinds of edges that are not * contained explicitly in the source object itself, such as from a weakmap key * to its value. These "implicit edges" are represented in some other - * container object, such as the weakmap itself. In these cases, calling unmark - * gray on an object won't find all of its children. + * container object, such as the weakmap itself. In these + * cases, calling unmark gray on an object won't find all of its children. * * Handling these implicit edges has two parts: * - A special pass enumerating all of the containers that know about the @@ -3087,13 +3125,12 @@ static bool TypedUnmarkGrayCellRecursively(T* t) { MOZ_ASSERT(t); - JSRuntime* rt = t->runtimeFromActiveCooperatingThread(); MOZ_ASSERT(!JS::CurrentThreadIsHeapCollecting()); MOZ_ASSERT(!JS::CurrentThreadIsHeapCycleCollecting()); UnmarkGrayTracer unmarker(rt); - gcstats::AutoPhase outerPhase(rt->gc.stats(), gcstats::PHASE_BARRIER); + gcstats::AutoPhase outerPhase(rt->gc.stats(), gcstats::PHASE_BARRIER); gcstats::AutoPhase innerPhase(rt->gc.stats(), gcstats::PHASE_UNMARK_GRAY); unmarker.unmark(JS::GCCellPtr(t, MapTypeToTraceKind::kind)); return unmarker.unmarkedAny; @@ -3106,7 +3143,7 @@ struct UnmarkGrayCellRecursivelyFunctor { bool js::UnmarkGrayCellRecursively(Cell* cell, JS::TraceKind kind) { - return DispatchTraceKindTyped(UnmarkGrayCellRecursivelyFunctor(), cell, kind); + return DispatchTraceKindTyped(UnmarkGrayCellRecursivelyFunctor(), cell, kind); } bool diff --git a/js/src/gc/Nursery-inl.h b/js/src/gc/Nursery-inl.h index 8ebfd82323..9c8bfac4e0 100644 --- a/js/src/gc/Nursery-inl.h +++ b/js/src/gc/Nursery-inl.h @@ -29,6 +29,62 @@ js::Nursery::getForwardedPointer(JSObject** ref) const return true; } +inline void +js::Nursery::maybeSetForwardingPointer(JSTracer* trc, void* oldData, void* newData, bool direct) +{ + if (trc->isTenuringTracer()) + setForwardingPointerWhileTenuring(oldData, newData, direct); +} + +inline void +js::Nursery::setForwardingPointerWhileTenuring(void* oldData, void* newData, bool direct) +{ + if (isInside(oldData)) + setForwardingPointer(oldData, newData, direct); +} + +inline void +js::Nursery::setSlotsForwardingPointer(HeapSlot* oldSlots, HeapSlot* newSlots, uint32_t nslots) +{ + // Slot arrays always have enough space for a forwarding pointer, since the + // number of slots is never zero. + MOZ_ASSERT(nslots > 0); + setDirectForwardingPointer(oldSlots, newSlots); +} + +inline void +js::Nursery::setElementsForwardingPointer(ObjectElements* oldHeader, ObjectElements* newHeader, + uint32_t capacity) +{ + // Only use a direct forwarding pointer if there is enough space for one. + setForwardingPointer(oldHeader->elements(), newHeader->elements(), + capacity > 0); +} + +inline void +js::Nursery::setForwardingPointer(void* oldData, void* newData, bool direct) +{ + if (direct) { + setDirectForwardingPointer(oldData, newData); + return; + } + + setIndirectForwardingPointer(oldData, newData); +} + +inline void +js::Nursery::setDirectForwardingPointer(void* oldData, void* newData) +{ + MOZ_ASSERT(isInside(oldData)); + + // Bug 1196210: If a zero-capacity header lands in the last 2 words of a + // jemalloc chunk abutting the start of a nursery chunk, the (invalid) + // newData pointer will appear to be "inside" the nursery. + MOZ_ASSERT(!isInside(newData) || (uintptr_t(newData) & js::gc::ChunkMask) == 0); + + *reinterpret_cast(oldData) = newData; +} + namespace js { // The allocation methods below will not run the garbage collector. If the diff --git a/js/src/gc/Nursery.cpp b/js/src/gc/Nursery.cpp index e6caf45a47..417a186a57 100644 --- a/js/src/gc/Nursery.cpp +++ b/js/src/gc/Nursery.cpp @@ -119,7 +119,7 @@ js::Nursery::Nursery(JSRuntime* rt) {} bool -js::Nursery::init(uint32_t maxNurseryBytes, AutoLockGC& lock) +js::Nursery::init(uint32_t maxNurseryBytes, AutoLockGCBgAlloc& lock) { if (!mallocedBuffers.init()) return false; @@ -135,11 +135,7 @@ js::Nursery::init(uint32_t maxNurseryBytes, AutoLockGC& lock) if (maxNurseryChunks_ == 0) return true; - if (!cellsWithUid_.init()) - return false; - - AutoMaybeStartBackgroundAllocation maybeBgAlloc; - updateNumChunksLocked(1, maybeBgAlloc, lock); + updateNumChunksLocked(1, lock); if (numChunks() == 0) return false; @@ -313,6 +309,19 @@ js::Nursery::allocateBuffer(JSObject* obj, size_t nbytes) return allocateBuffer(obj->zone(), nbytes); } +void* +js::Nursery::allocateBufferSameLocation(JSObject* obj, size_t nbytes) +{ + MOZ_ASSERT(obj); + MOZ_ASSERT(nbytes > 0); + MOZ_ASSERT(nbytes <= MaxNurseryBufferSize); + + if (!IsInsideNursery(obj)) + return obj->zone()->pod_malloc(nbytes); + + return allocate(nbytes); +} + void* js::Nursery::reallocateBuffer(JSObject* obj, void* oldBuffer, size_t oldBytes, size_t newBytes) @@ -347,7 +356,7 @@ js::Nursery::freeBuffer(void* buffer) } void -Nursery::setForwardingPointer(void* oldData, void* newData, bool direct) +Nursery::setIndirectForwardingPointer(void* oldData, void* newData) { MOZ_ASSERT(isInside(oldData)); @@ -356,37 +365,15 @@ Nursery::setForwardingPointer(void* oldData, void* newData, bool direct) // newData pointer will appear to be "inside" the nursery. MOZ_ASSERT(!isInside(newData) || (uintptr_t(newData) & ChunkMask) == 0); - if (direct) { - *reinterpret_cast(oldData) = newData; - } else { - AutoEnterOOMUnsafeRegion oomUnsafe; - if (!forwardedBuffers.initialized() && !forwardedBuffers.init()) - oomUnsafe.crash("Nursery::setForwardingPointer"); + AutoEnterOOMUnsafeRegion oomUnsafe; + if (!forwardedBuffers.initialized() && !forwardedBuffers.init()) + oomUnsafe.crash("Nursery::setForwardingPointer"); #ifdef DEBUG - if (ForwardedBufferMap::Ptr p = forwardedBuffers.lookup(oldData)) - MOZ_ASSERT(p->value() == newData); + if (ForwardedBufferMap::Ptr p = forwardedBuffers.lookup(oldData)) + MOZ_ASSERT(p->value() == newData); #endif - if (!forwardedBuffers.put(oldData, newData)) - oomUnsafe.crash("Nursery::setForwardingPointer"); - } -} - -void -Nursery::setSlotsForwardingPointer(HeapSlot* oldSlots, HeapSlot* newSlots, uint32_t nslots) -{ - // Slot arrays always have enough space for a forwarding pointer, since the - // number of slots is never zero. - MOZ_ASSERT(nslots > 0); - setForwardingPointer(oldSlots, newSlots, /* direct = */ true); -} - -void -Nursery::setElementsForwardingPointer(ObjectElements* oldHeader, ObjectElements* newHeader, - uint32_t capacity) -{ - // Only use a direct forwarding pointer if there is enough space for one. - setForwardingPointer(oldHeader->elements(), newHeader->elements(), - capacity > 0); + if (!forwardedBuffers.put(oldData, newData)) + oomUnsafe.crash("Nursery::setForwardingPointer"); } #ifdef DEBUG @@ -432,6 +419,22 @@ js::TenuringTracer::TenuringTracer(JSRuntime* rt, Nursery* nursery) { } +inline float +js::Nursery::calcPromotionRate(bool *validForTenuring) const { + float used = float(previousGC.nurseryUsedBytes); + float capacity = float(previousGC.nurseryCapacity); + float tenured = float(previousGC.tenuredBytes); + + if (validForTenuring) { + /* + * We can only use promotion rates if they're likely to be valid, + * they're only valid if the nursury was at least 90% full. + */ + *validForTenuring = used > capacity * 0.9f; + } + return tenured / used; +} + void js::Nursery::renderProfileJSON(JSONPrinter& json) const { @@ -457,8 +460,7 @@ js::Nursery::renderProfileJSON(JSONPrinter& json) const json.property("reason", JS::gcreason::ExplainReason(previousGC.reason)); json.property("bytes_tenured", previousGC.tenuredBytes); - json.floatProperty("promotion_rate", - 100.0 * previousGC.tenuredBytes / double(previousGC.nurseryUsedBytes), 2); + json.floatProperty("promotion_rate", calcPromotionRate(nullptr), 0); json.property("nursery_bytes", previousGC.nurseryUsedBytes); json.property("new_nursery_bytes", numChunks() * ChunkSize); @@ -559,14 +561,18 @@ js::Nursery::collect(JSRuntime* rt, JS::gcreason::Reason reason) JS::AutoSuppressGCAnalysis nogc; TenureCountCache tenureCounts; - double promotionRate = 0; previousGC.reason = JS::gcreason::NO_REASON; - if (!isEmpty()) - promotionRate = doCollection(rt, reason, tenureCounts); + if (!isEmpty()) { + doCollection(reason, tenureCounts); + } else { + previousGC.nurseryUsedBytes = 0; + previousGC.nurseryCapacity = spaceToEnd(); + previousGC.tenuredBytes = 0; + } // Resize the nursery. startProfile(ProfileKey::Resize); - maybeResizeNursery(reason, promotionRate); + maybeResizeNursery(reason); endProfile(ProfileKey::Resize); // If we are promoting the nursery, or exhausted the store buffer with @@ -574,16 +580,20 @@ js::Nursery::collect(JSRuntime* rt, JS::gcreason::Reason reason) // the nursery is full, look for object groups that are getting promoted // excessively and try to pretenure them. startProfile(ProfileKey::Pretenure); + bool validPromotionRate; + const float promotionRate = calcPromotionRate(&validPromotionRate); uint32_t pretenureCount = 0; - if (promotionRate > 0.8 || IsFullStoreBufferReason(reason)) { - JSContext* cx = TlsContext.get(); - for (auto& entry : tenureCounts.entries) { - if (entry.count >= 3000) { - ObjectGroup* group = entry.group; - if (group->canPreTenure()) { - AutoCompartment ac(cx, group->compartment()); - group->setShouldPreTenure(cx); - pretenureCount++; + if (validPromotionRate) { + if (promotionRate > 0.8 || IsFullStoreBufferReason(reason)) { + JSContext* cx = TlsContext.get(); + for (auto& entry : tenureCounts.entries) { + if (entry.count >= 3000) { + ObjectGroup* group = entry.group; + if (group->canPreTenure()) { + AutoCompartment ac(cx, group); + group->setShouldPreTenure(cx); + pretenureCount++; + } } } } @@ -633,8 +643,8 @@ js::Nursery::collect(JSRuntime* rt, JS::gcreason::Reason reason) } } -double -js::Nursery::doCollection(JSRuntime* rt, JS::gcreason::Reason reason, +void +js::Nursery::doCollection(JS::gcreason::Reason reason, TenureCountCache& tenureCounts) { AutoTraceSession session(rt, JS::HeapState::MinorCollecting); @@ -642,7 +652,8 @@ js::Nursery::doCollection(JSRuntime* rt, JS::gcreason::Reason reason, AutoDisableProxyCheck disableStrictProxyChecking(rt); mozilla::DebugOnly oomUnsafeRegion; - size_t initialNurserySize = spaceToEnd(); + const size_t initialNurseryCapacity = spaceToEnd(); + const size_t initialNurseryUsedBytes = initialNurseryCapacity - freeSpace(); // Move objects pointed to by roots from the nursery to the major heap. TenuringTracer mover(rt, this); @@ -701,11 +712,11 @@ js::Nursery::doCollection(JSRuntime* rt, JS::gcreason::Reason reason, collectToFixedPoint(mover, tenureCounts); endProfile(ProfileKey::CollectToFP); - // Sweep compartments to update the array buffer object's view lists. - startProfile(ProfileKey::SweepArrayBufferViewList); - for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) - c->sweepAfterMinorGC(&mover); - endProfile(ProfileKey::SweepArrayBufferViewList); + // Sweep to update any pointers to nursery objects that have now been + // tenured. + startProfile(ProfileKey::Sweep); + sweep(&mover); + endProfile(ProfileKey::Sweep); // Update any slot or element pointers whose destination has been tenured. startProfile(ProfileKey::UpdateJitActivations); @@ -722,9 +733,9 @@ js::Nursery::doCollection(JSRuntime* rt, JS::gcreason::Reason reason, freeMallocedBuffers(); endProfile(ProfileKey::FreeMallocedBuffers); - startProfile(ProfileKey::Sweep); - sweep(); - endProfile(ProfileKey::Sweep); + startProfile(ProfileKey::ClearNursery); + clear(); + endProfile(ProfileKey::ClearNursery); startProfile(ProfileKey::ClearStoreBuffer); runtime()->gc.storeBuffer().clear(); @@ -739,11 +750,9 @@ js::Nursery::doCollection(JSRuntime* rt, JS::gcreason::Reason reason, endProfile(ProfileKey::CheckHashTables); previousGC.reason = reason; - previousGC.nurseryUsedBytes = initialNurserySize; + previousGC.nurseryCapacity = initialNurseryCapacity; + previousGC.nurseryUsedBytes = initialNurseryUsedBytes; previousGC.tenuredBytes = mover.tenuredSize; - - // Calculate and return the promotion rate. - return mover.tenuredSize / double(initialNurserySize); } void @@ -797,20 +806,30 @@ js::Nursery::waitBackgroundFreeEnd() } void -js::Nursery::sweep() +js::Nursery::sweep(JSTracer* trc) { - /* Sweep unique id's in all in-use chunks. */ + // Sweep unique IDs first before we sweep any tables that may be keyed based + // on them. for (Cell* cell : cellsWithUid_) { JSObject* obj = static_cast(cell); - if (!IsForwarded(obj)) + if (!IsForwarded(obj)) { obj->zone()->removeUniqueId(obj); - else - MOZ_ASSERT(Forwarded(obj)->zone()->hasUniqueId(Forwarded(obj))); + } else { + JSObject* dst = Forwarded(obj); + dst->zone()->transferUniqueId(dst, obj); + } } cellsWithUid_.clear(); - sweepDictionaryModeObjects(); + for (CompartmentsIter c(runtime(), SkipAtoms); !c.done(); c.next()) + c->sweepAfterMinorGC(trc); + sweepDictionaryModeObjects(); +} + +void +js::Nursery::clear() +{ #ifdef JS_GC_ZEAL /* Poison the nursery contents so touching a freed object will crash. */ for (unsigned i = 0; i < numChunks(); i++) @@ -864,7 +883,7 @@ js::Nursery::setStartPosition() } void -js::Nursery::maybeResizeNursery(JS::gcreason::Reason reason, double promotionRate) +js::Nursery::maybeResizeNursery(JS::gcreason::Reason reason) { static const double GrowThreshold = 0.05; static const double ShrinkThreshold = 0.01; @@ -883,11 +902,19 @@ js::Nursery::maybeResizeNursery(JS::gcreason::Reason reason, double promotionRat return; #endif + /* + * This incorrect promotion rate results in better nursery sizing + * decisions, however we should to better tuning based on the real + * promotion rate in the future. + */ + const float promotionRate = + float(previousGC.tenuredBytes) / float(previousGC.nurseryCapacity); + newMaxNurseryChunks = runtime()->gc.tunables.gcMaxNurseryBytes() >> ChunkShift; if (newMaxNurseryChunks != maxNurseryChunks_) { maxNurseryChunks_ = newMaxNurseryChunks; /* The configured maximum nursery size is changing */ - int extraChunks = numChunks() - newMaxNurseryChunks; + const int extraChunks = numChunks() - newMaxNurseryChunks; if (extraChunks > 0) { /* We need to shrink the nursery */ shrinkAllocableSpace(extraChunks); @@ -931,16 +958,14 @@ void js::Nursery::updateNumChunks(unsigned newCount) { if (numChunks() != newCount) { - AutoMaybeStartBackgroundAllocation maybeBgAlloc; - AutoLockGC lock(runtime()); - updateNumChunksLocked(newCount, maybeBgAlloc, lock); + AutoLockGCBgAlloc lock(runtime()); + updateNumChunksLocked(newCount, lock); } } void js::Nursery::updateNumChunksLocked(unsigned newCount, - AutoMaybeStartBackgroundAllocation& maybeBgAlloc, - AutoLockGC& lock) + AutoLockGCBgAlloc& lock) { // The GC nursery is an optimization and so if we fail to allocate nursery // chunks we do not report an error. @@ -961,7 +986,7 @@ js::Nursery::updateNumChunksLocked(unsigned newCount, return; for (unsigned i = priorCount; i < newCount; i++) { - auto newChunk = runtime()->gc.getOrAllocChunk(lock, maybeBgAlloc); + auto newChunk = runtime()->gc.getOrAllocChunk(lock); if (!newChunk) { chunks_.shrinkTo(i); return; @@ -1014,6 +1039,8 @@ js::Nursery::sweepDictionaryModeObjects() for (auto obj : dictionaryModeObjects_) { if (!IsForwarded(obj)) obj->sweepDictionaryListPointer(); + else + Forwarded(obj)->updateDictionaryListPointerAfterMinorGC(obj); } dictionaryModeObjects_.clear(); } diff --git a/js/src/gc/Nursery.h b/js/src/gc/Nursery.h index 21cae4c4de..9b31463d3f 100644 --- a/js/src/gc/Nursery.h +++ b/js/src/gc/Nursery.h @@ -39,11 +39,11 @@ _(ClearNewObjectCache, "clrNOC") \ _(CollectToFP, "collct") \ _(ObjectsTenuredCallback, "tenCB") \ - _(SweepArrayBufferViewList, "swpABO") \ + _(Sweep, "sweep") \ _(UpdateJitActivations, "updtIn") \ _(FreeMallocedBuffers, "frSlts") \ _(ClearStoreBuffer, "clrSB") \ - _(Sweep, "sweep") \ + _(ClearNursery, "clear") \ _(Resize, "resize") \ _(Pretenure, "pretnr") @@ -54,6 +54,7 @@ struct Zone; namespace js { class ObjectElements; +class PlainObject; class NativeObject; class Nursery; class HeapSlot; @@ -93,8 +94,6 @@ class TenuringTracer : public JSTracer template void traverse(T** thingp); template 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); @@ -103,8 +102,12 @@ class TenuringTracer : public JSTracer private: Nursery& nursery() { return nursery_; } - JSObject* moveToTenured(JSObject* src); - size_t moveObjectToTenured(JSObject* dst, JSObject* src, gc::AllocKind dstKind); + inline void insertIntoFixupList(gc::RelocationOverlay* entry); + template + inline T* allocTenured(JS::Zone* zone, gc::AllocKind kind); + + inline JSObject* movePlainObjectToTenured(PlainObject* src); + JSObject* moveToTenuredSlow(JSObject* src); size_t moveElementsToTenured(NativeObject* dst, NativeObject* src, gc::AllocKind dstKind); size_t moveSlotsToTenured(NativeObject* dst, NativeObject* src, gc::AllocKind dstKind); @@ -134,7 +137,7 @@ class Nursery explicit Nursery(JSRuntime* rt); ~Nursery(); - MOZ_MUST_USE bool init(uint32_t maxNurseryBytes, AutoLockGC& lock); + [[nodiscard]] bool init(uint32_t maxNurseryBytes, AutoLockGCBgAlloc& lock); unsigned maxChunks() const { return maxNurseryChunks_; } unsigned numChunks() const { return chunks_.length(); } @@ -181,6 +184,13 @@ class Nursery */ void* allocateBuffer(JSObject* obj, size_t nbytes); + /* + * Allocate a buffer for a given object, always using the nursery if obj is + * in the nursery. The requested size must be less than or equal to + * MaxNurseryBufferSize. + */ + void* allocateBufferSameLocation(JSObject* obj, size_t nbytes); + /* Resize an existing object buffer. */ void* reallocateBuffer(JSObject* obj, void* oldBuffer, size_t oldBytes, size_t newBytes); @@ -204,10 +214,8 @@ class Nursery /* 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); - } + inline void maybeSetForwardingPointer(JSTracer* trc, void* oldData, void* newData, bool direct); + inline void setForwardingPointerWhileTenuring(void* oldData, void* newData, bool direct); /* Mark a malloced buffer as no longer needing to be freed. */ void removeMallocedBuffer(void* buffer) { @@ -308,7 +316,7 @@ class Nursery unsigned maxNurseryChunks_; /* Promotion rate for the previous minor collection. */ - double previousPromotionRate_; + float previousPromotionRate_; /* Report minor collections taking at least this many us, if enabled. */ int64_t profileThreshold_; @@ -335,12 +343,28 @@ class Nursery ProfileTimes totalTimes_; uint64_t minorGcCount_; + /* + * This data is initialised only if the nursery is enabled and after at + * least one call to Nursery::collect() + */ struct { JS::gcreason::Reason reason; - uint64_t nurseryUsedBytes; - uint64_t tenuredBytes; + size_t nurseryCapacity; + size_t nurseryUsedBytes; + size_t tenuredBytes; } previousGC; + /* + * Calculate the promotion rate of the most recent minor GC. + * The valid_for_tenuring parameter is used to return whether this + * promotion rate is accurate enough (the nursery was full enough) to be + * used for tenuring and other decisions. + * + * Must only be called if the previousGC data is initialised. + */ + float + calcPromotionRate(bool *validForTenuring) const; + /* * The set of externally malloced buffers potentially kept live by objects * stored in the nursery. Any external buffers that do not belong to a @@ -395,8 +419,7 @@ class Nursery void updateNumChunks(unsigned newCount); void updateNumChunksLocked(unsigned newCount, - gc::AutoMaybeStartBackgroundAllocation& maybeBgAlloc, - AutoLockGC& lock); + AutoLockGCBgAlloc& lock); MOZ_ALWAYS_INLINE uintptr_t allocationEnd() const { MOZ_ASSERT(numChunks() > 0); @@ -424,7 +447,7 @@ class Nursery /* Common internal allocator function. */ void* allocate(size_t size); - double doCollection(JSRuntime* rt, JS::gcreason::Reason reason, + void doCollection(JS::gcreason::Reason reason, gc::TenureCountCache& tenureCounts); /* @@ -434,26 +457,35 @@ class Nursery 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); + inline 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 capacity); + inline void setDirectForwardingPointer(void* oldData, void* newData); + void setIndirectForwardingPointer(void* oldData, void* newData); + + inline void setSlotsForwardingPointer(HeapSlot* oldSlots, HeapSlot* newSlots, uint32_t nslots); + inline void setElementsForwardingPointer(ObjectElements* oldHeader, ObjectElements* newHeader, + uint32_t capacity); /* Free malloced pointers owned by freed things in the nursery. */ void freeMallocedBuffers(); + /* + * Updates pointers to nursery objects that have been tenured and discards + * pointers to objects that have been freed. + */ + void sweep(JSTracer* trc); + /* * Frees all non-live nursery-allocated things at the end of a minor * collection. */ - void sweep(); + void clear(); void runSweepActions(); void sweepDictionaryModeObjects(); /* Change the allocable space provided by the nursery. */ - void maybeResizeNursery(JS::gcreason::Reason reason, double promotionRate); + void maybeResizeNursery(JS::gcreason::Reason reason); void growAllocableSpace(); void shrinkAllocableSpace(unsigned removeNumChunks); void minimizeAllocableSpace(); diff --git a/js/src/gc/Policy.h b/js/src/gc/Policy.h index 53bf1dc1a5..2800542564 100644 --- a/js/src/gc/Policy.h +++ b/js/src/gc/Policy.h @@ -47,6 +47,7 @@ class RegExpObject; class SavedFrame; class Scope; class EnvironmentObject; +class RequestedModuleObject; class ScriptSourceObject; class Shape; class SharedArrayBufferObject; @@ -87,6 +88,7 @@ class JitCode; D(js::PropertyName*) \ D(js::RegExpObject*) \ D(js::RegExpShared*) \ + D(js::RequestedModuleObject*) \ D(js::SavedFrame*) \ D(js::Scope*) \ D(js::ScriptSourceObject*) \ diff --git a/js/src/gc/Rooting.h b/js/src/gc/Rooting.h index 322414b4b0..97c54f9bf7 100644 --- a/js/src/gc/Rooting.h +++ b/js/src/gc/Rooting.h @@ -76,6 +76,85 @@ typedef JS::GCVector PropertyNameVector; typedef JS::GCVector ShapeVector; typedef JS::GCVector StringVector; +/** Interface substitute for Rooted which does not root the variable's memory. */ +template +class MOZ_RAII FakeRooted : public RootedBase> +{ + public: + using ElementType = T; + + template + explicit FakeRooted(CX* cx) : ptr(JS::GCPolicy::initial()) {} + + template + FakeRooted(CX* cx, T initial) : ptr(initial) {} + + DECLARE_POINTER_CONSTREF_OPS(T); + DECLARE_POINTER_ASSIGN_OPS(FakeRooted, T); + DECLARE_NONPOINTER_ACCESSOR_METHODS(ptr); + DECLARE_NONPOINTER_MUTABLE_ACCESSOR_METHODS(ptr); + + private: + T ptr; + + void set(const T& value) { + ptr = value; + } + + FakeRooted(const FakeRooted&) = delete; +}; + +/** Interface substitute for MutableHandle which is not required to point to rooted memory. */ +template +class FakeMutableHandle : public js::MutableHandleBase> +{ + public: + using ElementType = T; + + MOZ_IMPLICIT FakeMutableHandle(T* t) { + ptr = t; + } + + MOZ_IMPLICIT FakeMutableHandle(FakeRooted* root) { + ptr = root->address(); + } + + void set(const T& v) { + *ptr = v; + } + + DECLARE_POINTER_CONSTREF_OPS(T); + DECLARE_NONPOINTER_ACCESSOR_METHODS(*ptr); + DECLARE_NONPOINTER_MUTABLE_ACCESSOR_METHODS(*ptr); + + private: + FakeMutableHandle() {} + DELETE_ASSIGNMENT_OPS(FakeMutableHandle, T); + + T* ptr; +}; + +template class MaybeRooted +{ + public: + typedef const T& HandleType; + typedef FakeRooted RootType; + typedef FakeMutableHandle MutableHandleType; + + static JS::Handle toHandle(HandleType v) { + MOZ_CRASH("Bad conversion"); + } + + static JS::MutableHandle toMutableHandle(MutableHandleType v) { + MOZ_CRASH("Bad conversion"); + } + + template + static inline T2* downcastHandle(HandleType v) { + return &v->template as(); + } +}; + } /* namespace js */ #endif /* gc_Rooting_h */ diff --git a/js/src/gc/StoreBuffer.cpp b/js/src/gc/StoreBuffer.cpp index 067bd50f30..1005372893 100644 --- a/js/src/gc/StoreBuffer.cpp +++ b/js/src/gc/StoreBuffer.cpp @@ -27,11 +27,9 @@ StoreBuffer::GenericBuffer::trace(StoreBuffer* owner, JSTracer* trc) return; for (LifoAlloc::Enum e(*storage_); !e.empty();) { - unsigned size = *e.get(); - e.popFront(); - BufferableRef* edge = e.get(size); + unsigned size = *e.read(); + BufferableRef* edge = e.read(size); edge->trace(trc); - e.popFront(size); } } @@ -132,10 +130,8 @@ js::gc::AllocateWholeCellSet(Arena* arena) AutoEnterOOMUnsafeRegion oomUnsafe; Nursery& nursery = rt->gc.nursery; void* data = nursery.allocateBuffer(zone, sizeof(ArenaCellSet)); - if (!data) { + if (!data) oomUnsafe.crash("Failed to allocate WholeCellSet"); - return nullptr; - } if (nursery.freeSpace() < ArenaCellSet::NurseryFreeThresholdBytes) rt->gc.storeBuffer.setAboutToOverflow(); diff --git a/js/src/gc/Verifier.cpp b/js/src/gc/Verifier.cpp index bd2753fa8a..2c31250f81 100644 --- a/js/src/gc/Verifier.cpp +++ b/js/src/gc/Verifier.cpp @@ -192,12 +192,16 @@ gc::GCRuntime::startVerifyPreBarriers() if (!trc) return; - AutoPrepareForTracing prep(TlsContext.get(), WithAtoms); + JSContext* cx = TlsContext.get(); + AutoPrepareForTracing prep(cx, WithAtoms); - for (auto chunk = allNonEmptyChunks(); !chunk.done(); chunk.next()) - chunk->bitmap.clear(); + { + AutoLockGC lock(cx->runtime()); + for (auto chunk = allNonEmptyChunks(lock); !chunk.done(); chunk.next()) + chunk->bitmap.clear(); + } - gcstats::AutoPhase ap(stats(), gcstats::PHASE_TRACE_HEAP); + gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::TRACE_HEAP); const size_t size = 64 * 1024 * 1024; trc->root = (VerifyNode*)js_malloc(size); @@ -452,7 +456,18 @@ class HeapCheckTracerBase : public JS::CallbackTracer public: explicit CheckHeapTracer(JSRuntime* rt); bool init(); - void check(AutoLockForExclusiveAccess& lock); + bool traceHeap(AutoLockForExclusiveAccess& lock); + virtual void checkCell(Cell* cell) = 0; + + protected: + void dumpCellInfo(Cell* cell); + void dumpCellPath(); + + Cell* parentCell() { + return parentIndex == -1 ? nullptr : stack[parentIndex].thing.asCell(); + } + + size_t failures; private: void onChild(const JS::GCCellPtr& thing) override; @@ -512,19 +527,13 @@ CheckHeapTracer::onChild(const JS::GCCellPtr& thing) return; } - if (!IsValidGCThingPointer(cell) || !IsGCThingValidAfterMovingGC(cell)) - { - failures++; - fprintf(stderr, "Bad pointer %p\n", cell); - const char* name = contextName(); - for (int index = parentIndex; index != -1; index = stack[index].parentIndex) { - const WorkItem& parent = stack[index]; - cell = parent.thing.asCell(); - fprintf(stderr, " from %s %p %s edge\n", - GCTraceKindToAscii(cell->getTraceKind()), cell, name); - name = parent.name; - } - fprintf(stderr, " from root %s\n", name); + // Don't trace into GC things owned by another runtime. + if (cell->runtimeFromAnyThread() != rt) + return; + + // Don't trace into GC in zones being used by helper threads. + Zone* zone = thing.is() ? thing.as().zone() : cell->asTenured().zone(); + if (zone->group() && zone->group()->usedByHelperThread()) return; } @@ -552,7 +561,72 @@ CheckHeapTracer::check(AutoLockForExclusiveAccess& lock) } } - if (oom) + return !oom; +} + +void +HeapCheckTracerBase::dumpCellInfo(Cell* cell) +{ + auto kind = cell->getTraceKind(); + fprintf(stderr, "%s", GCTraceKindToAscii(kind)); + if (kind == JS::TraceKind::Object) + fprintf(stderr, " %s", static_cast(cell)->getClass()->name); + fprintf(stderr, " %p", cell); +} + +void +HeapCheckTracerBase::dumpCellPath() +{ + const char* name = contextName(); + for (int index = parentIndex; index != -1; index = stack[index].parentIndex) { + const WorkItem& parent = stack[index]; + Cell* cell = parent.thing.asCell(); + fprintf(stderr, " from "); + dumpCellInfo(cell); + fprintf(stderr, " %s edge\n", name); + name = parent.name; + } + fprintf(stderr, " from root %s\n", name); +} + +#endif // defined(JSGC_HASH_TABLE_CHECKS) || defined(DEBUG) + +#ifdef JSGC_HASH_TABLE_CHECKS + +class CheckHeapTracer final : public HeapCheckTracerBase +{ + public: + explicit CheckHeapTracer(JSRuntime* rt); + void check(AutoLockForExclusiveAccess& lock); + + private: + void checkCell(Cell* cell) override; +}; + +CheckHeapTracer::CheckHeapTracer(JSRuntime* rt) + : HeapCheckTracerBase(rt, TraceWeakMapKeysValues) +{} + +inline static bool +IsValidGCThingPointer(Cell* cell) +{ + return (uintptr_t(cell) & CellAlignMask) == 0; +} + +void +CheckHeapTracer::checkCell(Cell* cell) +{ + if (!IsValidGCThingPointer(cell) || !IsGCThingValidAfterMovingGC(cell)) { + failures++; + fprintf(stderr, "Bad pointer %p\n", cell); + dumpCellPath(); + } +} + +void +CheckHeapTracer::check(AutoLockForExclusiveAccess& lock) +{ + if (!traceHeap(lock)) return; if (failures) { @@ -596,16 +670,14 @@ void CheckGrayMarkingTracer::checkCell(Cell* cell) { Cell* parent = parentCell(); - if (!cell->isTenured() || !parent || !parent->isTenured()) + if (!parent) return; - TenuredCell* tenuredCell = &cell->asTenured(); - TenuredCell* tenuredParent = &parent->asTenured(); - if (tenuredParent->isMarkedBlack() && tenuredCell->isMarkedGray()) - { + if (parent->isMarkedBlack() && cell->isMarkedGray()) { failures++; - fprintf(stderr, "Found black to gray edge to %s %p\n", - GCTraceKindToAscii(cell->getTraceKind()), cell); + fprintf(stderr, "Found black to gray edge to "); + dumpCellInfo(cell); + fprintf(stderr, "\n"); dumpCellPath(); } } diff --git a/js/src/gc/Zone.cpp b/js/src/gc/Zone.cpp index 8cdc12dd3d..fac11ed781 100644 --- a/js/src/gc/Zone.cpp +++ b/js/src/gc/Zone.cpp @@ -55,10 +55,10 @@ JS::Zone::Zone(JSRuntime* rt) #endif jitZone_(group, nullptr), gcScheduled_(false), - gcPreserveCode_(false), - jitUsingBarriers_(false), - keepShapeTables_(false), - listNext_(NotOnList) + gcScheduledSaved_(false), + gcPreserveCode_(group, false), + keepShapeTables_(group, false), + listNext_(group, NotOnList) { /* Ensure that there are no vtables to mess us up here. */ MOZ_ASSERT(reinterpret_cast(this) == @@ -80,9 +80,12 @@ Zone::~Zone() js_delete(jitZone_); #ifdef DEBUG - // Avoid assertion destroying the weak map list if the embedding leaked GC things. - if (!rt->gc.shutdownCollectedEverything()) - gcWeakMapList.clear(); + // Avoid assertions failures warning that not everything has been destroyed + // if the embedding leaked GC things. + if (!rt->gc.shutdownCollectedEverything()) { + gcWeakMapList().clear(); + regExps.clear(); + } #endif } @@ -101,12 +104,6 @@ bool Zone::init(bool isSystemArg) void Zone::setNeedsIncrementalBarrier(bool needs, ShouldUpdateJit updateJit) { - if (updateJit == UpdateJit && needs != jitUsingBarriers_) { - jit::ToggleBarriers(this, needs); - jitUsingBarriers_ = needs; - } - - MOZ_ASSERT_IF(needs && isAtomsZone(), !runtimeFromMainThread()->exclusiveThreadsPresent()); MOZ_ASSERT_IF(needs, canCollect()); needsIncrementalBarrier_ = needs; } @@ -314,13 +311,14 @@ Zone::hasMarkedCompartments() bool Zone::canCollect() { - // Zones cannot be collected while in use by other threads. - if (usedByExclusiveThread) - return false; - JSRuntime* rt = runtimeFromAnyThread(); - if (isAtomsZone() && rt->exclusiveThreadsPresent()) - return false; - return true; + // The atoms zone cannot be collected while off-thread parsing is taking + // place. + if (isAtomsZone()) + return !runtimeFromAnyThread()->hasHelperThreadZones(); + + // Zones that will be or are currently used by other threads cannot be + // collected. + return !group()->createdForHelperThread(); } void diff --git a/js/src/gc/Zone.h b/js/src/gc/Zone.h index 2eee24a2be..ccf6e4fa76 100644 --- a/js/src/gc/Zone.h +++ b/js/src/gc/Zone.h @@ -45,9 +45,6 @@ class ZoneHeapThreshold double gcHeapGrowthFactor() const { return gcHeapGrowthFactor_; } size_t gcTriggerBytes() const { return gcTriggerBytes_; } - size_t AllocThresholdFactorTriggerBytes(GCSchedulingTunables& tunables) const { - return gcTriggerBytes_ * tunables.allocThresholdFactor(); - } double eagerAllocTrigger(bool highFrequencyGC) const; void updateAfterGC(size_t lastBytes, JSGCInvocationKind gckind, @@ -197,7 +194,7 @@ struct Zone : public JS::shadow::Zone, bool canCollect(); - void notifyObservingDebuggers(); + void notifyObservingDebuggers(); void setGCState(GCState state) { MOZ_ASSERT(CurrentThreadIsHeapBusy()); @@ -406,10 +403,9 @@ struct Zone : public JS::shadow::Zone, void updateMallocCounter(size_t nbytes) { updateMemoryCounter(gcMallocCounter, nbytes); } - void adoptMallocBytes(Zone* other) { + void adoptMallocBytes(Zone* other) { gcMallocCounter.adopt(other->gcMallocCounter); } - size_t GCMaxMallocBytes() const { return gcMallocCounter.maxBytes(); } size_t GCMallocBytes() const { return gcMallocCounter.bytes(); } @@ -426,7 +422,6 @@ struct Zone : public JS::shadow::Zone, gcMallocCounter.updateOnGCEnd(gc.tunables, lock); jitCodeCounter.updateOnGCEnd(gc.tunables, lock); } - js::gc::TriggerKind shouldTriggerGCForTooMuchMalloc() { auto& gc = runtimeFromAnyThread()->gc; return std::max(gcMallocCounter.shouldTriggerGC(gc.tunables), @@ -448,7 +443,7 @@ struct Zone : public JS::shadow::Zone, // Amount of data to allocate before triggering a new incremental slice for // the current GC. - js::ActiveThreadData gcDelayBytes; + js::UnprotectedData gcDelayBytes; // Shared Shape property tree. js::PropertyTree propertyTree; @@ -474,10 +469,9 @@ struct Zone : public JS::shadow::Zone, bool isSystem; - mozilla::Atomic usedByExclusiveThread; - - // True when there are active frames. - bool active; + bool usedByHelperThread() { + return !isAtomsZone() && group()->usedByHelperThread(); + } #ifdef DEBUG js::ZoneGroupData gcLastSweepGroupIndex; @@ -550,6 +544,7 @@ struct Zone : public JS::shadow::Zone, MOZ_ASSERT(!IsInsideNursery(tgt)); MOZ_ASSERT(js::CurrentThreadCanAccessRuntime(runtimeFromActiveCooperatingThread())); MOZ_ASSERT(js::CurrentThreadCanAccessZone(this)); + MOZ_ASSERT(!uniqueIds().has(tgt)); uniqueIds().rekeyIfMoved(src, tgt); } @@ -615,11 +610,10 @@ struct Zone : public JS::shadow::Zone, private: js::jit::JitZone* jitZone_; - GCState gcState_; - bool gcScheduled_; - bool gcPreserveCode_; - bool jitUsingBarriers_; - bool keepShapeTables_; + js::ActiveThreadData gcScheduled_; + js::ActiveThreadData gcScheduledSaved_; + js::ZoneGroupData gcPreserveCode_; + js::ZoneGroupData keepShapeTables_; // Allow zones to be linked into a list friend class js::gc::ZoneList; @@ -636,6 +630,41 @@ struct Zone : public JS::shadow::Zone, namespace js { +// Iterate over all zone groups except those which may be in use by helper +// thread parse tasks. +class ZoneGroupsIter +{ + gc::AutoEnterIteration iterMarker; + ZoneGroup** it; + ZoneGroup** end; + + public: + explicit ZoneGroupsIter(JSRuntime* rt) : iterMarker(&rt->gc) { + it = rt->gc.groups().begin(); + end = rt->gc.groups().end(); + + if (!done() && (*it)->usedByHelperThread()) + next(); + } + + bool done() const { return it == end; } + + void next() { + MOZ_ASSERT(!done()); + do { + it++; + } while (!done() && (*it)->usedByHelperThread()); + } + + ZoneGroup* get() const { + MOZ_ASSERT(!done()); + return *it; + } + + operator ZoneGroup*() const { return get(); } + ZoneGroup* operator->() const { return get(); } +}; + // Using the atoms zone without holding the exclusive access lock is dangerous // because worker threads may be using it simultaneously. Therefore, it's // better to skip the atoms zone when iterating over zones. If you need to diff --git a/js/src/gc/ZoneGroup.cpp b/js/src/gc/ZoneGroup.cpp index 9cfd2128cd..6ade20cb33 100644 --- a/js/src/gc/ZoneGroup.cpp +++ b/js/src/gc/ZoneGroup.cpp @@ -20,7 +20,7 @@ ZoneGroup::ZoneGroup(JSRuntime* runtime) ownerContext_(TlsContext.get()), enterCount(1), zones_(this), - usedByHelperThread(false), + helperThreadUse(HelperThreadUse::None), #ifdef DEBUG ionBailAfter_(this, 0), #endif @@ -45,6 +45,7 @@ ZoneGroup::init() ZoneGroup::~ZoneGroup() { #ifdef DEBUG + MOZ_ASSERT(helperThreadUse == HelperThreadUse::None); { AutoLockHelperThreadState lock; MOZ_ASSERT(ionLazyLinkListSize_ == 0); @@ -64,8 +65,8 @@ ZoneGroup::enter(JSContext* cx) if (ownerContext().context() == cx) { MOZ_ASSERT(enterCount); } else { - if (useExclusiveLocking) { - MOZ_ASSERT(!usedByHelperThread); + if (useExclusiveLocking()) { + MOZ_ASSERT(!usedByHelperThread()); while (ownerContext().context() != nullptr) { cx->yieldToEmbedding(); } diff --git a/js/src/gc/ZoneGroup.h b/js/src/gc/ZoneGroup.h new file mode 100644 index 0000000000..499f88578a --- /dev/null +++ b/js/src/gc/ZoneGroup.h @@ -0,0 +1,161 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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_ZoneGroup_h +#define gc_ZoneGroup_h + +#include "jsgc.h" + +#include "gc/Statistics.h" +#include "vm/Caches.h" +#include "vm/Stack.h" + +namespace js { + +namespace jit { class JitZoneGroup; } + +class AutoKeepAtoms; + +typedef Vector ZoneVector; + +// Zone groups encapsulate data about a group of zones that are logically +// related in some way. +// +// Zone groups are the primary means by which threads ensure exclusive access +// to the data they are using. Most data in a zone group, its zones, +// compartments, GC things and so forth may only be used by the thread that has +// entered the zone group. + +class ZoneGroup +{ + public: + JSRuntime* const runtime; + + private: + // The context with exclusive access to this zone group. + UnprotectedData ownerContext_; + + // The number of times the context has entered this zone group. + UnprotectedData enterCount; + + // If this flag is true, then we may need to block before entering this zone + // group. Blocking happens using JSContext::yieldToEmbedding. + UnprotectedData useExclusiveLocking_; + + public: + CooperatingContext& ownerContext() { return ownerContext_.ref(); } + void* addressOfOwnerContext() { return &ownerContext_.ref().cx; } + + void enter(JSContext* cx); + void leave(); + bool ownedByCurrentThread(); + + // All zones in the group. + private: + ZoneGroupOrGCTaskData zones_; + public: + ZoneVector& zones() { return zones_.ref(); } + + private: + enum class HelperThreadUse : uint32_t + { + None, + Pending, + Active + }; + + mozilla::Atomic helperThreadUse; + + public: + // Whether a zone in this group was created for use by a helper thread. + bool createdForHelperThread() const { + return helperThreadUse != HelperThreadUse::None; + } + // Whether a zone in this group is currently in use by a helper thread. + bool usedByHelperThread() const { + 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; + } + + explicit ZoneGroup(JSRuntime* runtime); + ~ZoneGroup(); + + bool init(); + + inline Nursery& nursery(); + inline gc::StoreBuffer& storeBuffer(); + + inline bool isCollecting(); + inline bool isGCScheduled(); + + // See the useExclusiveLocking_ field above. + void setUseExclusiveLocking() { useExclusiveLocking_ = true; } + bool useExclusiveLocking() { return useExclusiveLocking_; } + + // Delete an empty zone after its contents have been merged. + void deleteEmptyZone(Zone* zone); + +#ifdef DEBUG + private: + // The number of possible bailing places encounters before forcefully bailing + // in that place. Zero means inactive. + ZoneGroupData ionBailAfter_; + + public: + void* addressOfIonBailAfter() { return &ionBailAfter_; } + + // Set after how many bailing places we should forcefully bail. + // Zero disables this feature. + void setIonBailAfter(uint32_t after) { + ionBailAfter_ = after; + } +#endif + + ZoneGroupData jitZoneGroup; + + private: + /* Linked list of all Debugger objects in the group. */ + ZoneGroupData> debuggerList_; + public: + mozilla::LinkedList& debuggerList() { return debuggerList_.ref(); } + + // Number of Ion compilations which were finished off thread and are + // waiting to be lazily linked. This is only set while holding the helper + // thread state lock, but may be read from at other times. + mozilla::Atomic numFinishedBuilders; + + private: + /* List of Ion compilation waiting to get linked. */ + typedef mozilla::LinkedList IonBuilderList; + + js::HelperThreadLockData ionLazyLinkList_; + js::HelperThreadLockData ionLazyLinkListSize_; + + public: + IonBuilderList& ionLazyLinkList(); + + size_t ionLazyLinkListSize() { + return ionLazyLinkListSize_; + } + + void ionLazyLinkListRemove(js::jit::IonBuilder* builder); + void ionLazyLinkListAdd(js::jit::IonBuilder* builder); +}; + +} // namespace js + +#endif // gc_Zone_h