Simplify incremental GC sweeping

Splitting several parts of `SweepPhase` into constituent parts to
separate out sweeping code from code that handles yielding to the
mutator and resuming.
This commit is contained in:
Moonchild 2023-06-27 03:04:39 +02:00 • committed by roytam1
commit 95a1e4b7a3
4 changed files with 196 additions and 92 deletions

View file

@ -35,6 +35,12 @@ class MarkingValidator;
class AutoTraceSession; class AutoTraceSession;
struct MovingTracer; struct MovingTracer;
enum IncrementalProgress
{
NotFinished = 0,
Finished
};
class ChunkPool class ChunkPool
{ {
Chunk* head_; Chunk* head_;
@ -737,6 +743,8 @@ class GCRuntime
bool isShrinkingGC() const { return invocationKind == GC_SHRINK; } bool isShrinkingGC() const { return invocationKind == GC_SHRINK; }
static bool initializeSweepActions();
void setGrayRootsTracer(JSTraceDataOp traceOp, void* data); void setGrayRootsTracer(JSTraceDataOp traceOp, void* data);
MOZ_MUST_USE bool addBlackRootsTracer(JSTraceDataOp traceOp, void* data); MOZ_MUST_USE bool addBlackRootsTracer(JSTraceDataOp traceOp, void* data);
void removeBlackRootsTracer(JSTraceDataOp traceOp, void* data); void removeBlackRootsTracer(JSTraceDataOp traceOp, void* data);
@ -863,12 +871,6 @@ class GCRuntime
static TenuredCell* refillFreeListInGC(Zone* zone, AllocKind thingKind); static TenuredCell* refillFreeListInGC(Zone* zone, AllocKind thingKind);
private: private:
enum IncrementalProgress
{
NotFinished = 0,
Finished
};
// For ArenaLists::allocateFromArena() // For ArenaLists::allocateFromArena()
friend class ArenaLists; friend class ArenaLists;
Chunk* pickChunk(const AutoLockGC& lock, Chunk* pickChunk(const AutoLockGC& lock,
@ -951,7 +953,15 @@ class GCRuntime
void beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock); void beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock);
bool shouldReleaseObservedTypes(); bool shouldReleaseObservedTypes();
void endSweepingZoneGroup(); void endSweepingZoneGroup();
IncrementalProgress sweepPhase(SliceBudget& sliceBudget, AutoLockForExclusiveAccess& lock); IncrementalProgress performSweepActions(SliceBudget& sliceBudget, AutoLockForExclusiveAccess& lock);
static IncrementalProgress sweepTypeInformation(GCRuntime* gc, FreeOp* fop, Zone* zone,
SliceBudget& budget, AllocKind kind);
static IncrementalProgress mergeSweptObjectArenas(GCRuntime* gc, FreeOp* fop, Zone* zone,
SliceBudget& budget, AllocKind kind);
static IncrementalProgress finalizeAllocKind(GCRuntime* gc, FreeOp* fop, Zone* zone,
SliceBudget& budget, AllocKind kind);
static IncrementalProgress sweepShapeTree(GCRuntime* gc, FreeOp* fop, Zone* zone,
SliceBudget& budget, AllocKind kind);
void endSweepPhase(bool lastGC, AutoLockForExclusiveAccess& lock); void endSweepPhase(bool lastGC, AutoLockForExclusiveAccess& lock);
void sweepZones(FreeOp* fop, bool lastGC); void sweepZones(FreeOp* fop, bool lastGC);
void decommitAllWithoutUnlocking(const AutoLockGC& lock); void decommitAllWithoutUnlocking(const AutoLockGC& lock);
@ -1165,10 +1175,9 @@ class GCRuntime
*/ */
JS::Zone* zoneGroups; JS::Zone* zoneGroups;
JS::Zone* currentZoneGroup; JS::Zone* currentZoneGroup;
bool sweepingTypes; size_t sweepPhaseIndex;
unsigned finalizePhase;
JS::Zone* sweepZone; JS::Zone* sweepZone;
AllocKind sweepKind; size_t sweepActionIndex;
bool abortSweepAfterCurrentGroup; bool abortSweepAfterCurrentGroup;
/* /*

View file

@ -384,6 +384,42 @@ static const FinalizePhase BackgroundFinalizePhases[] = {
} }
}; };
// Incremental sweeping is controlled by a list of actions that describe what
// happens and in what order. Due to the incremental nature of sweeping an
// action does not necessarily run to completion so the current state is tracked
// in the GCRuntime by the performSweepActions() method.
//
// Actions are performed in phases run per sweep group, and each action is run
// for every zone in the group, i.e. as if by the following pseudocode:
//
// for each sweep group:
// for each phase:
// for each zone in sweep group:
// for each action in phase:
// perform_action
struct SweepAction
{
using Func = IncrementalProgress (*)(GCRuntime* gc, FreeOp* fop, Zone* zone,
SliceBudget& budget, AllocKind kind);
Func func;
AllocKind kind;
SweepAction(Func func, AllocKind kind) : func(func), kind(kind) {}
};
using SweepActionVector = Vector<SweepAction, 0, SystemAllocPolicy>;
using SweepPhaseVector = Vector<SweepActionVector, 0, SystemAllocPolicy>;
static SweepPhaseVector SweepPhases;
bool
js::gc::InitializeStaticData()
{
return GCRuntime::initializeSweepActions();
}
template<> template<>
JSObject* JSObject*
ArenaCellIterImpl::get<JSObject>() const ArenaCellIterImpl::get<JSObject>() const
@ -841,8 +877,9 @@ GCRuntime::GCRuntime(JSRuntime* rt) :
zoneGroupIndex(0), zoneGroupIndex(0),
zoneGroups(nullptr), zoneGroups(nullptr),
currentZoneGroup(nullptr), currentZoneGroup(nullptr),
sweepPhaseIndex(0),
sweepZone(nullptr), sweepZone(nullptr),
sweepKind(AllocKind::FIRST), sweepActionIndex(0),
abortSweepAfterCurrentGroup(false), abortSweepAfterCurrentGroup(false),
arenasAllocatedDuringSweep(nullptr), arenasAllocatedDuringSweep(nullptr),
startedCompacting(false), startedCompacting(false),
@ -883,7 +920,7 @@ static const uint64_t JIT_SCRIPT_RELEASE_TYPES_PERIOD = 20;
bool bool
GCRuntime::init(uint32_t maxbytes, uint32_t maxNurseryBytes) GCRuntime::init(uint32_t maxbytes, uint32_t maxNurseryBytes)
{ {
InitMemorySubsystem(); MOZ_ASSERT(SystemPageSize());
if (!rootsHash.init(256)) if (!rootsHash.init(256))
return false; return false;
@ -4667,11 +4704,9 @@ GCRuntime::beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock)
zone->arenas.queueForegroundThingsForSweep(&fop); zone->arenas.queueForegroundThingsForSweep(&fop);
} }
sweepingTypes = true; sweepPhaseIndex = 0;
finalizePhase = 0;
sweepZone = currentZoneGroup; sweepZone = currentZoneGroup;
sweepKind = AllocKind::FIRST; sweepActionIndex = 0;
{ {
gcstats::AutoPhase ap(stats, gcstats::PHASE_FINALIZE_END); gcstats::AutoPhase ap(stats, gcstats::PHASE_FINALIZE_END);
@ -4765,7 +4800,7 @@ ArenaLists::foregroundFinalize(FreeOp* fop, AllocKind thingKind, SliceBudget& sl
return true; return true;
} }
GCRuntime::IncrementalProgress IncrementalProgress
GCRuntime::drainMarkStack(SliceBudget& sliceBudget, gcstats::Phase phase) GCRuntime::drainMarkStack(SliceBudget& sliceBudget, gcstats::Phase phase)
{ {
/* Run a marking slice and return whether the stack is now empty. */ /* Run a marking slice and return whether the stack is now empty. */
@ -4810,104 +4845,159 @@ SweepArenaList(Arena** arenasToSweep, SliceBudget& sliceBudget, Args... args)
return true; return true;
} }
GCRuntime::IncrementalProgress /* static */ IncrementalProgress
GCRuntime::sweepPhase(SliceBudget& sliceBudget, AutoLockForExclusiveAccess& lock) GCRuntime::sweepTypeInformation(GCRuntime* gc, FreeOp* fop, Zone* zone, SliceBudget& budget,
AllocKind kind)
{
// Sweep dead type information stored in scripts and object groups, but
// don't finalize them yet. We have to sweep dead information from both live
// and dead scripts and object groups, so that no dead references remain in
// them. Type inference can end up crawling these zones again, such as for
// TypeCompartment::markSetsUnknown, and if this happens after sweeping for
// the sweep group finishes we won't be able to determine which things in
// the zone are live.
MOZ_ASSERT(kind == AllocKind::LIMIT);
gcstats::AutoPhase ap1(gc->stats, gcstats::PHASE_SWEEP_COMPARTMENTS);
gcstats::AutoPhase ap2(gc->stats, gcstats::PHASE_SWEEP_TYPES);
ArenaLists& al = zone->arenas;
AutoClearTypeInferenceStateOnOOM oom(zone);
if (!SweepArenaList<JSScript>(&al.gcScriptArenasToUpdate, budget, &oom))
return NotFinished;
if (!SweepArenaList<ObjectGroup>(&al.gcObjectGroupArenasToUpdate, budget, &oom))
return NotFinished;
// Finish sweeping type information in the zone.
{
gcstats::AutoPhase ap(gc->stats, gcstats::PHASE_SWEEP_TYPES_END);
zone->types.endSweep(gc->rt);
}
return Finished;
}
/* static */ IncrementalProgress
GCRuntime::mergeSweptObjectArenas(GCRuntime* gc, FreeOp* fop, Zone* zone, SliceBudget& budget,
AllocKind kind)
{
// Foreground finalized objects have already been finalized, and now their
// arenas can be reclaimed by freeing empty ones and making non-empty ones
// available for allocation.
MOZ_ASSERT(kind == AllocKind::LIMIT);
zone->arenas.mergeForegroundSweptObjectArenas();
return Finished;
}
/* static */ IncrementalProgress
GCRuntime::finalizeAllocKind(GCRuntime* gc, FreeOp* fop, Zone* zone, SliceBudget& budget,
AllocKind kind)
{
// Set the number of things per arena for this AllocKind.
size_t thingsPerArena = Arena::thingsPerArena(kind);
auto& sweepList = gc->incrementalSweepList;
sweepList.setThingsPerArena(thingsPerArena);
if (!zone->arenas.foregroundFinalize(fop, kind, budget, sweepList))
return NotFinished;
// Reset the slots of the sweep list that we used.
sweepList.reset(thingsPerArena);
return Finished;
}
/* static */ IncrementalProgress
GCRuntime::sweepShapeTree(GCRuntime* gc, FreeOp* fop, Zone* zone, SliceBudget& budget,
AllocKind kind)
{
// Remove dead shapes from the shape tree, but don't finalize them yet.
MOZ_ASSERT(kind == AllocKind::LIMIT);
gcstats::AutoPhase ap(gc->stats, gcstats::PHASE_SWEEP_SHAPE);
ArenaLists& al = zone->arenas;
if (!SweepArenaList<Shape>(&al.gcShapeArenasToUpdate, budget))
return NotFinished;
if (!SweepArenaList<AccessorShape>(&al.gcAccessorShapeArenasToUpdate, budget))
return NotFinished;
return Finished;
}
static void
AddSweepPhase(bool* ok)
{
if (*ok)
*ok = SweepPhases.emplaceBack();
}
static void
AddSweepAction(bool* ok, SweepAction::Func func, AllocKind kind = AllocKind::LIMIT)
{
if (*ok)
*ok = SweepPhases.back().emplaceBack(func, kind);
}
/* static */ bool
GCRuntime::initializeSweepActions()
{
bool ok = true;
AddSweepPhase(&ok);
AddSweepAction(&ok, GCRuntime::sweepTypeInformation);
AddSweepAction(&ok, GCRuntime::mergeSweptObjectArenas);
for (const auto& finalizePhase : IncrementalFinalizePhases) {
AddSweepPhase(&ok);
for (auto kind : finalizePhase.kinds)
AddSweepAction(&ok, GCRuntime::finalizeAllocKind, kind);
}
AddSweepPhase(&ok);
AddSweepAction(&ok, GCRuntime::sweepShapeTree);
return ok;
}
IncrementalProgress
GCRuntime::performSweepActions(SliceBudget& budget, AutoLockForExclusiveAccess& lock)
{ {
AutoSetThreadIsSweeping threadIsSweeping; AutoSetThreadIsSweeping threadIsSweeping;
gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP); gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP);
FreeOp fop(rt); FreeOp fop(rt);
if (drainMarkStack(sliceBudget, gcstats::PHASE_SWEEP_MARK) == NotFinished) if (drainMarkStack(budget, gcstats::PHASE_SWEEP_MARK) == NotFinished)
return NotFinished; return NotFinished;
for (;;) { for (;;) {
// Sweep dead type information stored in scripts and object groups, but for (; sweepPhaseIndex < SweepPhases.length(); sweepPhaseIndex++) {
// don't finalize them yet. We have to sweep dead information from both const auto& actions = SweepPhases[sweepPhaseIndex];
// live and dead scripts and object groups, so that no dead references
// remain in them. Type inference can end up crawling these zones
// again, such as for TypeCompartment::markSetsUnknown, and if this
// happens after sweeping for the zone group finishes we won't be able
// to determine which things in the zone are live.
if (sweepingTypes) {
gcstats::AutoPhase ap1(stats, gcstats::PHASE_SWEEP_COMPARTMENTS);
gcstats::AutoPhase ap2(stats, gcstats::PHASE_SWEEP_TYPES);
for (; sweepZone; sweepZone = sweepZone->nextNodeInGroup()) { for (; sweepZone; sweepZone = sweepZone->nextNodeInGroup()) {
ArenaLists& al = sweepZone->arenas; for (; sweepActionIndex < actions.length(); sweepActionIndex++) {
const auto& action = actions[sweepActionIndex];
AutoClearTypeInferenceStateOnOOM oom(sweepZone); if (action.func(this, &fop, sweepZone, budget, action.kind) == NotFinished)
if (!SweepArenaList<JSScript>(&al.gcScriptArenasToUpdate, sliceBudget, &oom))
return NotFinished;
if (!SweepArenaList<ObjectGroup>(
&al.gcObjectGroupArenasToUpdate, sliceBudget, &oom))
{
return NotFinished;
}
// Finish sweeping type information in the zone.
{
gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP_TYPES_END);
sweepZone->types.endSweep(rt);
}
// Foreground finalized objects have already been finalized,
// and now their arenas can be reclaimed by freeing empty ones
// and making non-empty ones available for allocation.
al.mergeForegroundSweptObjectArenas();
}
sweepZone = currentZoneGroup;
sweepingTypes = false;
}
/* Finalize foreground finalized things. */
for (; finalizePhase < ArrayLength(IncrementalFinalizePhases) ; ++finalizePhase) {
gcstats::AutoPhase ap(stats, IncrementalFinalizePhases[finalizePhase].statsPhase);
for (; sweepZone; sweepZone = sweepZone->nextNodeInGroup()) {
Zone* zone = sweepZone;
for (auto kind : SomeAllocKinds(sweepKind, AllocKind::LIMIT)) {
if (!IncrementalFinalizePhases[finalizePhase].kinds.contains(kind))
continue;
/* Set the number of things per arena for this AllocKind. */
size_t thingsPerArena = Arena::thingsPerArena(kind);
incrementalSweepList.setThingsPerArena(thingsPerArena);
if (!zone->arenas.foregroundFinalize(&fop, kind, sliceBudget,
incrementalSweepList))
{
sweepKind = kind;
return NotFinished; return NotFinished;
}
/* Reset the slots of the sweep list that we used. */
incrementalSweepList.reset(thingsPerArena);
} }
sweepKind = AllocKind::FIRST; // Reset action index to first.
sweepActionIndex = 0;
} }
sweepZone = currentZoneGroup; sweepZone = currentZoneGroup;
} }
/* Remove dead shapes from the shape tree, but don't finalize them yet. */ // Reset phase index.
{ sweepPhaseIndex = 0;
gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP_SHAPE);
for (; sweepZone; sweepZone = sweepZone->nextNodeInGroup()) {
ArenaLists& al = sweepZone->arenas;
if (!SweepArenaList<Shape>(&al.gcShapeArenasToUpdate, sliceBudget))
return NotFinished;
if (!SweepArenaList<AccessorShape>(&al.gcAccessorShapeArenasToUpdate, sliceBudget))
return NotFinished;
}
}
endSweepingZoneGroup(); endSweepingZoneGroup();
getNextZoneGroup(); getNextZoneGroup();
@ -4999,7 +5089,7 @@ GCRuntime::beginCompactPhase()
startedCompacting = true; startedCompacting = true;
} }
GCRuntime::IncrementalProgress IncrementalProgress
GCRuntime::compactPhase(JS::gcreason::Reason reason, SliceBudget& sliceBudget, GCRuntime::compactPhase(JS::gcreason::Reason reason, SliceBudget& sliceBudget,
AutoLockForExclusiveAccess& lock) AutoLockForExclusiveAccess& lock)
{ {
@ -5381,7 +5471,7 @@ GCRuntime::incrementalCollectSlice(SliceBudget& budget, JS::gcreason::Reason rea
MOZ_FALLTHROUGH; MOZ_FALLTHROUGH;
case State::Sweep: case State::Sweep:
if (sweepPhase(budget, lock) == NotFinished) if (performSweepActions(budget, lock) == NotFinished)
break; break;
endSweepPhase(destroyingRuntime, lock); endSweepPhase(destroyingRuntime, lock);

View file

@ -838,6 +838,9 @@ class ArenaLists
/* The number of GC cycles an empty chunk can survive before been released. */ /* The number of GC cycles an empty chunk can survive before been released. */
const size_t MAX_EMPTY_CHUNK_AGE = 4; const size_t MAX_EMPTY_CHUNK_AGE = 4;
extern bool
InitializeStaticData();
} /* namespace gc */ } /* namespace gc */
class InterpreterFrame; class InterpreterFrame;

View file

@ -102,6 +102,8 @@ JS::detail::InitWithFailureDiagnostic(bool isDebugBuild)
RETURN_IF_FAIL(js::wasm::InitInstanceStaticData()); RETURN_IF_FAIL(js::wasm::InitInstanceStaticData());
js::gc::InitMemorySubsystem(); // Ensure gc::SystemPageSize() works. js::gc::InitMemorySubsystem(); // Ensure gc::SystemPageSize() works.
RETURN_IF_FAIL(js::gc::InitializeStaticData());
RETURN_IF_FAIL(js::jit::InitProcessExecutableMemory()); RETURN_IF_FAIL(js::jit::InitProcessExecutableMemory());
MOZ_ALWAYS_TRUE(js::MemoryProtectionExceptionHandler::install()); MOZ_ALWAYS_TRUE(js::MemoryProtectionExceptionHandler::install());