@@ -1485,7 +1485,6 @@ completed_scavenge(GCState *gcstate)
14851485 gc_list_merge (& gcstate -> old [visited ].head , & gcstate -> old [not_visited ].head );
14861486 gc_list_set_space (& gcstate -> old [not_visited ].head , not_visited );
14871487 }
1488- assert (gc_list_is_empty (& gcstate -> old [visited ].head ));
14891488 gcstate -> work_to_do = 0 ;
14901489 gcstate -> phase = GC_PHASE_MARK ;
14911490}
@@ -1709,6 +1708,7 @@ gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats)
17091708
17101709 if (gc_list_is_empty (not_visited )) {
17111710 completed_scavenge (gcstate );
1711+ assert (gc_list_is_empty (& gcstate -> old [visited ].head ));
17121712 }
17131713 validate_spaces (gcstate );
17141714}
@@ -1720,21 +1720,42 @@ gc_collect_full(PyThreadState *tstate,
17201720 GC_STAT_ADD (2 , collections , 1 );
17211721 GCState * gcstate = & tstate -> interp -> gc ;
17221722 validate_spaces (gcstate );
1723- PyGC_Head * young = & gcstate -> young .head ;
1724- PyGC_Head * pending = & gcstate -> old [gcstate -> visited_space ^1 ].head ;
1723+ PyGC_Head * not_visited = & gcstate -> old [gcstate -> visited_space ^1 ].head ;
17251724 PyGC_Head * visited = & gcstate -> old [gcstate -> visited_space ].head ;
1726- untrack_tuples (young );
1727- /* merge all generations into visited */
1728- gc_list_merge (young , pending );
1729- gc_list_validate_space (pending , 1 - gcstate -> visited_space );
1730- gc_list_set_space (pending , gcstate -> visited_space );
1731- gcstate -> young .count = 0 ;
1732- gc_list_merge (pending , visited );
1725+ untrack_tuples (& gcstate -> young .head );
1726+
1727+ // Move objects that are reachable from known roots into
1728+ // the "visited" set.
1729+ gc_list_set_space (visited , 1 - gcstate -> visited_space );
1730+ gc_list_merge (visited , not_visited );
1731+ Py_ssize_t objects_marked = mark_at_start (tstate );
1732+ GC_STAT_ADD (2 , objects_transitively_reachable , objects_marked );
1733+ stats -> candidates += objects_marked ;
17331734 validate_spaces (gcstate );
17341735
1735- gc_collect_region (tstate , visited , visited ,
1736- stats );
1736+ // Prepare increment set, it contains "young+not_visited". The "visited"
1737+ // set contains objects known to not be garbage and so they will be
1738+ // excluded from the gc_collect_region() operation.
1739+ PyGC_Head increment ;
1740+ gc_list_init (& increment );
1741+ gc_list_set_space (& gcstate -> young .head , gcstate -> visited_space );
1742+ gc_list_merge (& gcstate -> young .head , & increment );
1743+ gc_list_validate_space (& increment , gcstate -> visited_space );
1744+ gc_list_set_space (not_visited , gcstate -> visited_space );
1745+ gc_list_merge (not_visited , & increment );
1746+ validate_list (& increment , collecting_clear_unreachable_clear );
1747+ gc_list_validate_space (& increment , gcstate -> visited_space );
1748+
1749+ // Find cyclic garbage by comparing edge counts with reference counts.
1750+ // Objects that are not garbage will be in "survivors".
1751+ PyGC_Head survivors ;
1752+ gc_list_init (& survivors );
1753+ gc_collect_region (tstate , & increment , & survivors , stats );
1754+ gc_list_merge (& survivors , visited );
1755+ assert (gc_list_is_empty (& increment ));
1756+ assert (gc_list_is_empty (not_visited ));
17371757 validate_spaces (gcstate );
1758+
17381759 gcstate -> young .count = 0 ;
17391760 gcstate -> old [0 ].count = 0 ;
17401761 gcstate -> old [1 ].count = 0 ;
0 commit comments