# 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/. # flake8: noqa: F821 # Generate graph structures for GC statistics recording. # # Stats phases are nested and form a directed acyclic graph starting # from a set of root phases. Importantly, a phase may appear under more # than one parent phase. # # For example, the following arrangement is possible: # # +---+ # | A | # +---+ # | # +-------+-------+ # | | | # v v v # +---+ +---+ +---+ # | B | | C | | D | # +---+ +---+ +---+ # | | # +---+---+ # | # v # +---+ # | E | # +---+ # # This graph is expanded into a tree (or really a forest) and phases # with multiple parents are duplicated. # # For example, the input example above would be expanded to: # # +---+ # | A | # +---+ # | # +-------+-------+ # | | | # v v v # +---+ +---+ +---+ # | B | | C | | D | # +---+ +---+ +---+ # | | # v v # +---+ +---+ # | E | | E'| # +---+ +---+ # NOTE: If you add new phases here the current next phase kind number can be # found at the end of js/src/gc/StatsPhasesGenerated.inc import collections import re class PhaseKind: def __init__(self, name, descr, bucket, children=[]): self.name = name self.descr = descr # For telemetry self.bucket = bucket self.children = children AllPhaseKinds = [] PhaseKindsByName = dict() def addPhaseKind(name, descr, bucket, children=[]): assert name not in PhaseKindsByName phaseKind = PhaseKind(name, descr, bucket, children) AllPhaseKinds.append(phaseKind) PhaseKindsByName[name] = phaseKind return phaseKind def getPhaseKind(name): return PhaseKindsByName[name] PhaseKindGraphRoots = [ addPhaseKind("MUTATOR", "Mutator Running", 0), addPhaseKind("GC_BEGIN", "Begin Callback", 1), addPhaseKind( "EVICT_NURSERY_FOR_MAJOR_GC", "Evict Nursery For Major GC", 70, [ addPhaseKind( "MARK_ROOTS", "Mark Roots", 48, [ addPhaseKind("MARK_CCWS", "Mark Cross Compartment Wrappers", 50), addPhaseKind("MARK_STACK", "Mark C and JS stacks", 51), addPhaseKind("MARK_RUNTIME_DATA", "Mark Runtime-wide Data", 52), addPhaseKind("MARK_EMBEDDING", "Mark Embedding", 53), ], ) ], ), addPhaseKind("WAIT_BACKGROUND_THREAD", "Wait Background Thread", 2), addPhaseKind( "PREPARE", "Prepare For Collection", 69, [ addPhaseKind("UNMARK", "Unmark", 7), addPhaseKind("UNMARK_WEAKMAPS", "Unmark WeakMaps", 76), addPhaseKind("MARK_DISCARD_CODE", "Mark Discard Code", 3), addPhaseKind("RELAZIFY_FUNCTIONS", "Relazify Functions", 4), addPhaseKind("PURGE", "Purge", 5), addPhaseKind("PURGE_PROP_MAP_TABLES", "Purge PropMapTables", 60), addPhaseKind("PURGE_SOURCE_URLS", "Purge Source URLs", 73), addPhaseKind("JOIN_PARALLEL_TASKS", "Join Parallel Tasks", 67), ], ), addPhaseKind( "MARK", "Mark", 6, [ getPhaseKind("MARK_ROOTS"), addPhaseKind("MARK_DELAYED", "Mark Delayed", 8), addPhaseKind( "MARK_WEAK", "Mark Weak", 13, [ getPhaseKind("MARK_DELAYED"), addPhaseKind("MARK_GRAY_WEAK", "Mark Gray and Weak", 16), ], ), addPhaseKind("MARK_INCOMING_GRAY", "Mark Incoming Gray Pointers", 14), addPhaseKind("MARK_GRAY", "Mark Gray", 15), addPhaseKind( "PARALLEL_MARK", "Parallel marking", 78, [ getPhaseKind("JOIN_PARALLEL_TASKS"), # The following are only used for parallel phase times: addPhaseKind("PARALLEL_MARK_MARK", "Parallel marking work", 79), addPhaseKind("PARALLEL_MARK_WAIT", "Waiting for work", 80), ], ), ], ), addPhaseKind( "SWEEP", "Sweep", 9, [ getPhaseKind("MARK"), addPhaseKind( "FINALIZE_START", "Finalize Start Callbacks", 17, [ addPhaseKind("WEAK_ZONES_CALLBACK", "Per-Slice Weak Callback", 57), addPhaseKind( "WEAK_COMPARTMENT_CALLBACK", "Per-Compartment Weak Callback", 58 ), ], ), addPhaseKind("UPDATE_ATOMS_BITMAP", "Sweep Atoms Bitmap", 68), addPhaseKind("SWEEP_ATOMS_TABLE", "Sweep Atoms Table", 18), addPhaseKind( "SWEEP_COMPARTMENTS", "Sweep Compartments", 20, [ addPhaseKind("SWEEP_DISCARD_CODE", "Sweep Discard Code", 21), addPhaseKind("SWEEP_INNER_VIEWS", "Sweep Inner Views", 22), addPhaseKind( "SWEEP_CC_WRAPPER", "Sweep Cross Compartment Wrappers", 23 ), addPhaseKind("SWEEP_BASE_SHAPE", "Sweep Base Shapes", 24), addPhaseKind("SWEEP_INITIAL_SHAPE", "Sweep Initial Shapes", 25), addPhaseKind("SWEEP_REGEXP", "Sweep Regexps", 28), addPhaseKind("SWEEP_COMPRESSION", "Sweep Compression Tasks", 62), addPhaseKind("SWEEP_WEAKMAPS", "Sweep WeakMaps", 63), addPhaseKind("SWEEP_UNIQUEIDS", "Sweep Unique IDs", 64), addPhaseKind( "SWEEP_FINALIZATION_OBSERVERS", "Sweep FinalizationRegistries and WeakRefs", 74, ), addPhaseKind("SWEEP_JIT_DATA", "Sweep JIT Data", 65), addPhaseKind("SWEEP_WEAK_CACHES", "Sweep Weak Caches", 66), addPhaseKind("SWEEP_MISC", "Sweep Miscellaneous", 29), getPhaseKind("JOIN_PARALLEL_TASKS"), ], ), addPhaseKind("FINALIZE_OBJECT", "Finalize Objects", 33), addPhaseKind("FINALIZE_NON_OBJECT", "Finalize Non-objects", 34), addPhaseKind("SWEEP_PROP_MAP", "Sweep PropMap Tree", 77), addPhaseKind("FINALIZE_END", "Finalize End Callback", 38), addPhaseKind("DESTROY", "Deallocate", 39), getPhaseKind("JOIN_PARALLEL_TASKS"), addPhaseKind("FIND_DEAD_COMPARTMENTS", "Find Dead Compartments", 54), ], ), addPhaseKind( "COMPACT", "Compact", 40, [ addPhaseKind("COMPACT_MOVE", "Compact Move", 41), addPhaseKind( "COMPACT_UPDATE", "Compact Update", 42, [ getPhaseKind("MARK_ROOTS"), addPhaseKind("COMPACT_UPDATE_CELLS", "Compact Update Cells", 43), getPhaseKind("JOIN_PARALLEL_TASKS"), ], ), ], ), addPhaseKind("DECOMMIT", "Decommit", 72), addPhaseKind("GC_END", "End Callback", 44), addPhaseKind( "MINOR_GC", "All Minor GCs", 45, [ getPhaseKind("MARK_ROOTS"), ], ), addPhaseKind( "EVICT_NURSERY", "Minor GCs to Evict Nursery", 46, [ getPhaseKind("MARK_ROOTS"), ], ), addPhaseKind( "TRACE_HEAP", "Trace Heap", 47, [ getPhaseKind("MARK_ROOTS"), ], ), ] class Phase: # Expand the DAG into a tree, duplicating phases which have more than # one parent. def __init__(self, phaseKind, parent): self.phaseKind = phaseKind self.parent = parent self.depth = parent.depth + 1 if parent else 0 self.children = [] self.nextSibling = None self.nextInPhaseKind = None self.path = re.sub(r"\W+", "_", phaseKind.name.lower()) if parent is not None: self.path = parent.path + "." + self.path def expandPhases(): phases = [] phasesForKind = collections.defaultdict(list) def traverse(phaseKind, parent): ep = Phase(phaseKind, parent) phases.append(ep) # Update list of expanded phases for this phase kind. if phasesForKind[phaseKind]: phasesForKind[phaseKind][-1].nextInPhaseKind = ep phasesForKind[phaseKind].append(ep) # Recurse over children. for child in phaseKind.children: child_ep = traverse(child, ep) if ep.children: ep.children[-1].nextSibling = child_ep ep.children.append(child_ep) return ep for phaseKind in PhaseKindGraphRoots: traverse(phaseKind, None) return phases, phasesForKind AllPhases, PhasesForPhaseKind = expandPhases() # Name phases based on phase kind name and index if there are multiple phases # corresponding to a single phase kind. for phaseKind in AllPhaseKinds: phases = PhasesForPhaseKind[phaseKind] if len(phases) == 1: phases[0].name = "%s" % phaseKind.name else: for index, phase in enumerate(phases): phase.name = "%s_%d" % (phaseKind.name, index + 1) # Find the maximum phase nesting. MaxPhaseNesting = max(phase.depth for phase in AllPhases) + 1 # And the maximum bucket number. MaxBucket = max(kind.bucket for kind in AllPhaseKinds) # Generate code. def writeList(out, items): if items: out.write(",\n".join(" " + item for item in items) + "\n") def writeEnumClass(out, name, type, items, extraItems): items = ["FIRST"] + list(items) + ["LIMIT"] + list(extraItems) items[1] += " = " + items[0] out.write("enum class %s : %s {\n" % (name, type)) writeList(out, items) out.write("};\n") def generateHeader(out): # # Generate PhaseKind enum. # phaseKindNames = map(lambda phaseKind: phaseKind.name, AllPhaseKinds) extraPhaseKinds = [ "NONE = LIMIT", "EXPLICIT_SUSPENSION = LIMIT", "IMPLICIT_SUSPENSION", ] writeEnumClass(out, "PhaseKind", "uint8_t", phaseKindNames, extraPhaseKinds) out.write("\n") # # Generate Phase enum. # phaseNames = map(lambda phase: phase.name, AllPhases) extraPhases = ["NONE = LIMIT", "EXPLICIT_SUSPENSION = LIMIT", "IMPLICIT_SUSPENSION"] writeEnumClass(out, "Phase", "uint8_t", phaseNames, extraPhases) out.write("\n") # # Generate MAX_PHASE_NESTING constant. # out.write("static const size_t MAX_PHASE_NESTING = %d;\n" % MaxPhaseNesting) def generateCpp(out): # # Generate the PhaseKindInfo table. # out.write("static constexpr PhaseKindTable phaseKinds = {\n") for phaseKind in AllPhaseKinds: phase = PhasesForPhaseKind[phaseKind][0] out.write( ' /* PhaseKind::%s */ PhaseKindInfo { Phase::%s, %d, "%s" },\n' % (phaseKind.name, phase.name, phaseKind.bucket, phaseKind.name) ) out.write("};\n") out.write("\n") # # Generate the PhaseInfo tree. # def name(phase): return "Phase::" + phase.name if phase else "Phase::NONE" out.write("static constexpr PhaseTable phases = {\n") for phase in AllPhases: firstChild = phase.children[0] if phase.children else None phaseKind = phase.phaseKind out.write( ' /* %s */ PhaseInfo { %s, %s, %s, %s, PhaseKind::%s, %d, "%s", "%s" },\n' % ( # NOQA: E501 name(phase), name(phase.parent), name(firstChild), name(phase.nextSibling), name(phase.nextInPhaseKind), phaseKind.name, phase.depth, phaseKind.descr, phase.path, ) ) out.write("};\n") # # Print in a comment the next available phase kind number. # out.write("// The next available phase kind number is: %d\n" % (MaxBucket + 1))