58 a1 GC. Trying see when issue with atoms started.

58 a1 GC. Trying see when issue with atoms started.
This commit is contained in:
win7-7 2026-01-14 04:58:46 +02:00 committed by wuggy
commit f9e762101d
19 changed files with 907 additions and 356 deletions

View file

@ -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<AutoLockGC> maybeLock;
mozilla::Maybe<AutoLockGCBgAlloc> 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;

View file

@ -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

View file

@ -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<Value>
DispatchTyped(PreBarrierFunctor<Value>(), 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<T>,
protected:
void pre() { InternalBarrierMethods<T>::preBarrier(this->value); }
void post(const T& prev, const T& next) {
MOZ_ALWAYS_INLINE void post(const T& prev, const T& next) {
InternalBarrierMethods<T>::postBarrier(&this->value, prev, next);
}
};

View file

@ -23,6 +23,7 @@
namespace js {
class AutoLockGC;
class AutoLockGCBgAlloc;
class AutoLockHelperThreadState;
class VerifyPreTracer;
@ -31,7 +32,9 @@ namespace gc {
typedef Vector<JS::Zone*, 4, SystemAllocPolicy> ZoneVector;
using BlackGrayEdgeVector = Vector<TenuredCell*, 0, SystemAllocPolicy>;
class AutoMaybeStartBackgroundAllocation;
class AutoCallGCCallbacks;
class AutoRunParallelTask;
class AutoTraceSession;
class MarkingValidator;
class AutoTraceSession;
struct MovingTracer;
@ -119,28 +122,29 @@ class BackgroundDecommitTask : public GCParallelTaskHelper<BackgroundDecommitTas
class GCSchedulingTunables
{
/*
* Soft limit on the number of bytes we are allowed to allocate in the GC
* heap. Attempts to allocate gcthings over this limit will return null and
* subsequently invoke the standard OOM machinery, independent of available
* physical memory.
* JSGC_MAX_BYTES
*
* Maximum nominal heap before last ditch GC.
*/
UnprotectedData<size_t> gcMaxBytes_;
/*
* JSGC_MAX_MALLOC_BYTES
* JSGC_MAX_MALLOC_BYTES
*
* Initial malloc bytes threshold.
*/
UnprotectedData<size_t> 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<size_t> 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<float> 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<Chunk*, ChunkPool::Iter, ChunkPool::Iter> 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<ChunkPool> availableChunks_;
GCLockData<ChunkPool> 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<ChunkPool> fullChunks_;
GCLockData<ChunkPool> 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<JSGCMode> mode;
mozilla::Atomic<size_t, mozilla::ReleaseAcquire> numActiveZoneIters;
@ -1274,7 +1310,6 @@ class GCRuntime
ActiveThreadData<bool> useZeal;
#endif
/* Indicates that the last incremental slice exhausted the mark stack. */
ActiveThreadData<bool> 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<int64_t> 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<uint32_t> gcBeginCallbackDepth;
Callback<JSGCCallback> gcCallback;
Callback<JS::DoCycleCollectionCallback> gcDoCycleCollectionCallback;
Callback<JSObjectsTenuredCallback> 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 */

View file

@ -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

View file

@ -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<const T*>(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

View file

@ -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);
}

View file

@ -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<PlainObject>()) {
*objp = movePlainObjectToTenured(&obj->as<PlainObject>());
return;
}
*objp = moveToTenuredSlow(obj);
}
template <typename S>
@ -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<JSObject**>(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<JSObject*>(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<JSObject*>(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 <typename T>
@ -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 <typename T>
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<T*>(static_cast<Cell*>(t));
}
JSObject*
js::TenuringTracer::moveToTenuredSlow(JSObject* src)
{
MOZ_ASSERT(IsInsideNursery(src));
MOZ_ASSERT(!src->zone()->usedByHelperThread());
MOZ_ASSERT(!src->is<PlainObject>());
AllocKind dstKind = src->allocKindForTenure(nursery());
auto dst = allocTenured<JSObject>(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<ArrayObject>()) {
tenuredSize = srcSize = sizeof(NativeObject);
dstSize = srcSize = sizeof(NativeObject);
} else if (src->is<TypedArrayObject>()) {
TypedArrayObject* tarray = &src->as<TypedArrayObject>();
// 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<NativeObject>();
@ -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>()) {
InlineTypedObject::objectMovedDuringMinorGC(this, dst, src);
} else if (src->is<TypedArrayObject>()) {
tenuredSize += TypedArrayObject::objectMovedDuringMinorGC(this, dst, src, dstKind);
} else if (src->is<UnboxedArrayObject>()) {
tenuredSize += UnboxedArrayObject::objectMovedDuringMinorGC(this, dst, src, dstKind);
} else if (src->is<ArgumentsObject>()) {
tenuredSize += ArgumentsObject::objectMovedDuringMinorGC(this, dst, src);
} else if (src->is<ProxyObject>()) {
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<ProxyObject>(), 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<PlainObject>(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<JSObject*>(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<T>::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

View file

@ -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<void**>(oldData) = newData;
}
namespace js {
// The allocation methods below will not run the garbage collector. If the

View file

@ -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<uint8_t>(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<void**>(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<AutoEnterOOMUnsafeRegion> 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<JSObject*>(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();
}

View file

@ -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 <typename T> void traverse(T** thingp);
template <typename T> 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 <typename T>
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();

View file

@ -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*) \

View file

@ -76,6 +76,85 @@ typedef JS::GCVector<PropertyName*> PropertyNameVector;
typedef JS::GCVector<Shape*> ShapeVector;
typedef JS::GCVector<JSString*> StringVector;
/** Interface substitute for Rooted<T> which does not root the variable's memory. */
template <typename T>
class MOZ_RAII FakeRooted : public RootedBase<T, FakeRooted<T>>
{
public:
using ElementType = T;
template <typename CX>
explicit FakeRooted(CX* cx) : ptr(JS::GCPolicy<T>::initial()) {}
template <typename CX>
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<T> which is not required to point to rooted memory. */
template <typename T>
class FakeMutableHandle : public js::MutableHandleBase<T, FakeMutableHandle<T>>
{
public:
using ElementType = T;
MOZ_IMPLICIT FakeMutableHandle(T* t) {
ptr = t;
}
MOZ_IMPLICIT FakeMutableHandle(FakeRooted<T>* 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 <typename T> class MaybeRooted<T, NoGC>
{
public:
typedef const T& HandleType;
typedef FakeRooted<T> RootType;
typedef FakeMutableHandle<T> MutableHandleType;
static JS::Handle<T> toHandle(HandleType v) {
MOZ_CRASH("Bad conversion");
}
static JS::MutableHandle<T> toMutableHandle(MutableHandleType v) {
MOZ_CRASH("Bad conversion");
}
template <typename T2>
static inline T2* downcastHandle(HandleType v) {
return &v->template as<T2>();
}
};
} /* namespace js */
#endif /* gc_Rooting_h */

View file

@ -27,11 +27,9 @@ StoreBuffer::GenericBuffer::trace(StoreBuffer* owner, JSTracer* trc)
return;
for (LifoAlloc::Enum e(*storage_); !e.empty();) {
unsigned size = *e.get<unsigned>();
e.popFront<unsigned>();
BufferableRef* edge = e.get<BufferableRef>(size);
unsigned size = *e.read<unsigned>();
BufferableRef* edge = e.read<BufferableRef>(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();

View file

@ -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<JSObject>() ? thing.as<JSObject>().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<JSObject*>(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();
}
}

View file

@ -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<JS::shadow::Zone*>(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

View file

@ -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<size_t> gcDelayBytes;
js::UnprotectedData<size_t> gcDelayBytes;
// Shared Shape property tree.
js::PropertyTree propertyTree;
@ -474,10 +469,9 @@ struct Zone : public JS::shadow::Zone,
bool isSystem;
mozilla::Atomic<bool> usedByExclusiveThread;
// True when there are active frames.
bool active;
bool usedByHelperThread() {
return !isAtomsZone() && group()->usedByHelperThread();
}
#ifdef DEBUG
js::ZoneGroupData<unsigned> 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<bool> gcScheduled_;
js::ActiveThreadData<bool> gcScheduledSaved_;
js::ZoneGroupData<bool> gcPreserveCode_;
js::ZoneGroupData<bool> 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

View file

@ -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();
}

161
js/src/gc/ZoneGroup.h Normal file
View file

@ -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<JS::Zone*, 4, SystemAllocPolicy> 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<CooperatingContext> ownerContext_;
// The number of times the context has entered this zone group.
UnprotectedData<size_t> enterCount;
// If this flag is true, then we may need to block before entering this zone
// group. Blocking happens using JSContext::yieldToEmbedding.
UnprotectedData<bool> 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<ZoneVector> zones_;
public:
ZoneVector& zones() { return zones_.ref(); }
private:
enum class HelperThreadUse : uint32_t
{
None,
Pending,
Active
};
mozilla::Atomic<HelperThreadUse> 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<uint32_t> 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<jit::JitZoneGroup*> jitZoneGroup;
private:
/* Linked list of all Debugger objects in the group. */
ZoneGroupData<mozilla::LinkedList<js::Debugger>> debuggerList_;
public:
mozilla::LinkedList<js::Debugger>& 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<size_t> numFinishedBuilders;
private:
/* List of Ion compilation waiting to get linked. */
typedef mozilla::LinkedList<js::jit::IonBuilder> IonBuilderList;
js::HelperThreadLockData<IonBuilderList> ionLazyLinkList_;
js::HelperThreadLockData<size_t> 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