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

@ -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<>
JSObject*
ArenaCellIterImpl::get<JSObject>() const
@ -841,8 +877,9 @@ GCRuntime::GCRuntime(JSRuntime* rt) :
zoneGroupIndex(0),
zoneGroups(nullptr),
currentZoneGroup(nullptr),
sweepPhaseIndex(0),
sweepZone(nullptr),
sweepKind(AllocKind::FIRST),
sweepActionIndex(0),
abortSweepAfterCurrentGroup(false),
arenasAllocatedDuringSweep(nullptr),
startedCompacting(false),
@ -883,7 +920,7 @@ static const uint64_t JIT_SCRIPT_RELEASE_TYPES_PERIOD = 20;
bool
GCRuntime::init(uint32_t maxbytes, uint32_t maxNurseryBytes)
{
InitMemorySubsystem();
MOZ_ASSERT(SystemPageSize());
if (!rootsHash.init(256))
return false;
@ -4667,11 +4704,9 @@ GCRuntime::beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock)
zone->arenas.queueForegroundThingsForSweep(&fop);
}
sweepingTypes = true;
finalizePhase = 0;
sweepPhaseIndex = 0;
sweepZone = currentZoneGroup;
sweepKind = AllocKind::FIRST;
sweepActionIndex = 0;
{
gcstats::AutoPhase ap(stats, gcstats::PHASE_FINALIZE_END);
@ -4765,7 +4800,7 @@ ArenaLists::foregroundFinalize(FreeOp* fop, AllocKind thingKind, SliceBudget& sl
return true;
}
GCRuntime::IncrementalProgress
IncrementalProgress
GCRuntime::drainMarkStack(SliceBudget& sliceBudget, gcstats::Phase phase)
{
/* Run a marking slice and return whether the stack is now empty. */
@ -4810,105 +4845,160 @@ SweepArenaList(Arena** arenasToSweep, SliceBudget& sliceBudget, Args... args)
return true;
}
GCRuntime::IncrementalProgress
GCRuntime::sweepPhase(SliceBudget& sliceBudget, AutoLockForExclusiveAccess& lock)
/* static */ IncrementalProgress
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;
gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP);
FreeOp fop(rt);
if (drainMarkStack(sliceBudget, gcstats::PHASE_SWEEP_MARK) == NotFinished)
if (drainMarkStack(budget, gcstats::PHASE_SWEEP_MARK) == NotFinished)
return NotFinished;
for (;;) {
// 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 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 (; sweepPhaseIndex < SweepPhases.length(); sweepPhaseIndex++) {
const auto& actions = SweepPhases[sweepPhaseIndex];
for (; sweepZone; sweepZone = sweepZone->nextNodeInGroup()) {
ArenaLists& al = sweepZone->arenas;
AutoClearTypeInferenceStateOnOOM oom(sweepZone);
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;
for (; sweepActionIndex < actions.length(); sweepActionIndex++) {
const auto& action = actions[sweepActionIndex];
if (action.func(this, &fop, sweepZone, budget, action.kind) == 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;
}
/* Remove dead shapes from the shape tree, but don't finalize them yet. */
{
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;
}
}
// Reset phase index.
sweepPhaseIndex = 0;
endSweepingZoneGroup();
getNextZoneGroup();
if (!currentZoneGroup)
@ -4999,7 +5089,7 @@ GCRuntime::beginCompactPhase()
startedCompacting = true;
}
GCRuntime::IncrementalProgress
IncrementalProgress
GCRuntime::compactPhase(JS::gcreason::Reason reason, SliceBudget& sliceBudget,
AutoLockForExclusiveAccess& lock)
{
@ -5381,7 +5471,7 @@ GCRuntime::incrementalCollectSlice(SliceBudget& budget, JS::gcreason::Reason rea
MOZ_FALLTHROUGH;
case State::Sweep:
if (sweepPhase(budget, lock) == NotFinished)
if (performSweepActions(budget, lock) == NotFinished)
break;
endSweepPhase(destroyingRuntime, lock);