mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-07 16:28:38 +09:00
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:
parent
ba8872f5ce
commit
f9e762101d
19 changed files with 907 additions and 356 deletions
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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*) \
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
161
js/src/gc/ZoneGroup.h
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue