mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-06 15:58:39 +09:00
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:
parent
661754041c
commit
95a1e4b7a3
4 changed files with 196 additions and 92 deletions
272
js/src/jsgc.cpp
272
js/src/jsgc.cpp
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue