@@ -345,6 +345,8 @@ gc_list_merge(PyGC_Head *from, PyGC_Head *to)
345345 PyGC_Head * from_tail = GC_PREV (from );
346346 assert (from_head != from );
347347 assert (from_tail != from );
348+ assert (gc_list_is_empty (to ) ||
349+ gc_old_space (to_tail ) == gc_old_space (from_tail ));
348350
349351 _PyGCHead_SET_NEXT (to_tail , from_head );
350352 _PyGCHead_SET_PREV (from_head , to_tail );
@@ -413,8 +415,8 @@ enum flagstates {collecting_clear_unreachable_clear,
413415static void
414416validate_list (PyGC_Head * head , enum flagstates flags )
415417{
416- assert ((head -> _gc_prev & PREV_MASK_COLLECTING ) == 0 );
417- assert ((head -> _gc_next & NEXT_MASK_UNREACHABLE ) == 0 );
418+ assert ((head -> _gc_prev & ~ _PyGC_PREV_MASK ) == 0 );
419+ assert ((head -> _gc_next & ~ _PyGC_PREV_MASK ) == 0 );
418420 uintptr_t prev_value = 0 , next_value = 0 ;
419421 switch (flags ) {
420422 case collecting_clear_unreachable_clear :
@@ -488,9 +490,37 @@ validate_list_is_aging(PyGC_Head *head)
488490 assert (prev == GC_PREV (head ));
489491}
490492
493+ static void
494+ validate_consistent_old_space (PyGC_Head * head )
495+ {
496+ PyGC_Head * prev = head ;
497+ PyGC_Head * gc = GC_NEXT (head );
498+ if (gc == head ) {
499+ return ;
500+ }
501+ uintptr_t old_space = gc_old_space (gc );
502+ while (gc != head ) {
503+ PyGC_Head * truenext = GC_NEXT (gc );
504+ assert (truenext != NULL );
505+ assert (gc_old_space (gc ) == old_space );
506+ prev = gc ;
507+ gc = truenext ;
508+ }
509+ assert (prev == GC_PREV (head ));
510+ }
511+
512+ static void
513+ validate_list_header (PyGC_Head * head )
514+ {
515+ assert (GC_PREV (head ) == (PyGC_Head * )head -> _gc_prev );
516+ assert (GC_NEXT (head ) == (PyGC_Head * )head -> _gc_next );
517+ }
518+
491519#else
492520#define validate_old (g ) do{}while(0)
521+ #define validate_list_header (g ) do{}while(0)
493522#define validate_list_is_aging (l ) do{}while(0)
523+ #define validate_consistent_old_space (l ) do{}while(0)
494524#endif
495525
496526/*** end of list stuff ***/
@@ -618,7 +648,7 @@ visit_reachable(PyObject *op, PyGC_Head *reachable)
618648 prev -> _gc_next & NEXT_MASK_UNREACHABLE );
619649 _PyObject_ASSERT (FROM_GC (next ),
620650 next -> _gc_next & NEXT_MASK_UNREACHABLE );
621- prev -> _gc_next = gc -> _gc_next ; // copy NEXT_MASK_UNREACHABLE
651+ prev -> _gc_next = gc -> _gc_next ; // copy flag bits
622652 gc -> _gc_next &= ~NEXT_MASK_UNREACHABLE ;
623653 _PyGCHead_SET_PREV (next , prev );
624654
@@ -670,6 +700,9 @@ move_unreachable(PyGC_Head *young, PyGC_Head *unreachable)
670700 * or to the right have been scanned yet.
671701 */
672702
703+ validate_consistent_old_space (young );
704+ /* Record which old space we are in, and set NEXT_MASK_UNREACHABLE bit for convenience */
705+ uintptr_t flags = NEXT_MASK_UNREACHABLE | (gc -> _gc_next & _PyGC_NEXT_MASK_OLD_SPACE_1 );
673706 while (gc != young ) {
674707 if (gc_get_refs (gc )) {
675708 /* gc is definitely reachable from outside the
@@ -715,15 +748,16 @@ move_unreachable(PyGC_Head *young, PyGC_Head *unreachable)
715748 // But this may pollute the unreachable list head's 'next' pointer
716749 // too. That's semantically senseless but expedient here - the
717750 // damage is repaired when this function ends.
718- last -> _gc_next = ( NEXT_MASK_UNREACHABLE | (uintptr_t )gc ) ;
751+ last -> _gc_next = flags | (uintptr_t )gc ;
719752 _PyGCHead_SET_PREV (gc , last );
720- gc -> _gc_next = ( NEXT_MASK_UNREACHABLE | (uintptr_t )unreachable ) ;
753+ gc -> _gc_next = flags | (uintptr_t )unreachable ;
721754 unreachable -> _gc_prev = (uintptr_t )gc ;
722755 }
723756 gc = _PyGCHead_NEXT (prev );
724757 }
725758 // young->_gc_prev must be last element remained in the list.
726759 young -> _gc_prev = (uintptr_t )prev ;
760+ young -> _gc_next &= _PyGC_PREV_MASK ;
727761 // don't let the pollution of the list head's next pointer leak
728762 unreachable -> _gc_next &= _PyGC_PREV_MASK ;
729763}
@@ -782,8 +816,8 @@ move_legacy_finalizers(PyGC_Head *unreachable, PyGC_Head *finalizers)
782816 PyObject * op = FROM_GC (gc );
783817
784818 _PyObject_ASSERT (op , gc -> _gc_next & NEXT_MASK_UNREACHABLE );
785- gc -> _gc_next &= _PyGC_PREV_MASK ;
786- next = ( PyGC_Head * ) gc -> _gc_next ;
819+ next = GC_NEXT ( gc ) ;
820+ gc -> _gc_next &= ~ NEXT_MASK_UNREACHABLE ;
787821
788822 if (has_legacy_finalizer (op )) {
789823 gc_clear_collecting (gc );
@@ -802,8 +836,8 @@ clear_unreachable_mask(PyGC_Head *unreachable)
802836 assert ((unreachable -> _gc_next & NEXT_MASK_UNREACHABLE ) == 0 );
803837 for (gc = GC_NEXT (unreachable ); gc != unreachable ; gc = next ) {
804838 _PyObject_ASSERT ((PyObject * )FROM_GC (gc ), gc -> _gc_next & NEXT_MASK_UNREACHABLE );
805- gc -> _gc_next &= _PyGC_PREV_MASK ;
806- next = ( PyGC_Head * ) gc -> _gc_next ;
839+ next = GC_NEXT ( gc ) ;
840+ gc -> _gc_next &= ~ NEXT_MASK_UNREACHABLE ;
807841 }
808842 validate_list (unreachable , collecting_set_unreachable_clear );
809843}
@@ -1181,8 +1215,11 @@ deduce_unreachable(PyGC_Head *base, PyGC_Head *unreachable) {
11811215 * refcount greater than 0 when all the references within the
11821216 * set are taken into account).
11831217 */
1218+ validate_list_header (base );
11841219 update_refs (base ); // gc_prev is used for gc_refs
1220+ validate_list_header (base );
11851221 subtract_refs (base );
1222+ validate_list_header (base );
11861223
11871224 /* Leave everything reachable from outside base in base, and move
11881225 * everything else (in base) to unreachable.
@@ -1219,7 +1256,6 @@ deduce_unreachable(PyGC_Head *base, PyGC_Head *unreachable) {
12191256 * the reachable objects instead. But this is a one-time cost, probably not
12201257 * worth complicating the code to speed just a little.
12211258 */
1222- gc_list_init (unreachable );
12231259 move_unreachable (base , unreachable ); // gc_prev is pointer again
12241260 validate_list (base , collecting_clear_unreachable_clear );
12251261 validate_list (unreachable , collecting_set_unreachable_set );
@@ -1244,13 +1280,16 @@ handle_resurrected_objects(PyGC_Head *unreachable, PyGC_Head* still_unreachable,
12441280{
12451281 // Remove the PREV_MASK_COLLECTING from unreachable
12461282 // to prepare it for a new call to 'deduce_unreachable'
1283+ validate_list_header (unreachable );
12471284 gc_list_clear_collecting (unreachable );
12481285
12491286 // After the call to deduce_unreachable, the 'still_unreachable' set will
12501287 // have the PREV_MARK_COLLECTING set, but the objects are going to be
12511288 // removed so we can skip the expense of clearing the flag.
12521289 PyGC_Head * resurrected = unreachable ;
1290+ validate_list_header (resurrected );
12531291 deduce_unreachable (resurrected , still_unreachable );
1292+ validate_list_header (resurrected );
12541293 clear_unreachable_mask (still_unreachable );
12551294
12561295 // Move the resurrected objects to the old generation for future collection.
@@ -1437,7 +1476,6 @@ gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats)
14371476 PyGC_Head increment ;
14381477 gc_list_init (& increment );
14391478 Py_ssize_t work_to_do = - gcstate -> incremental_gc_progress ;
1440- Py_ssize_t region_size = 0 ;
14411479 validate_old (gcstate );
14421480 if (gc_list_is_empty (oldest )) {
14431481 if (gc_list_is_empty (aging )) {
@@ -1453,6 +1491,7 @@ gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats)
14531491 gcstate -> aging_space = flip_old_space (gcstate -> aging_space );
14541492 }
14551493 validate_old (gcstate );
1494+ Py_ssize_t region_size = 0 ;
14561495 while (region_size < work_to_do ) {
14571496 if (gc_list_is_empty (oldest )) {
14581497 gcstate -> incremental_gc_progress = 0 ;
@@ -1465,17 +1504,12 @@ gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats)
14651504 }
14661505 validate_old (gcstate );
14671506 validate_list_is_aging (& increment );
1468- #ifdef Py_STATS
1469- {
1470- Py_ssize_t count = 0 ;
1471- PyGC_Head * gc ;
1472- for (gc = GC_NEXT (& increment ); gc != & increment ; gc = GC_NEXT (gc )) {
1473- count ++ ;
1474- }
1475- GC_STAT_ADD (NUM_GENERATIONS , objects_queued , count );
1476- }
1477- #endif
1478- gc_collect_region (tstate , & increment , aging , 0 , stats );
1507+ GC_STAT_ADD (1 , objects_queued , region_size );
1508+ PyGC_Head survivors ;
1509+ gc_list_init (& survivors );
1510+ gc_collect_region (tstate , & increment , & survivors , 0 , stats );
1511+ validate_list_is_aging (& survivors );
1512+ gc_list_merge (& survivors , aging );
14791513 validate_old (gcstate );
14801514 assert (gc_list_is_empty (& increment ));
14811515 gcstate -> incremental_gc_progress += region_size ;
@@ -1525,8 +1559,6 @@ gc_collect_region(PyThreadState *tstate,
15251559 int untrack ,
15261560 struct gc_collection_stats * stats )
15271561{
1528- Py_ssize_t m = 0 ; /* # objects collected */
1529- Py_ssize_t n = 0 ; /* # unreachable objects that couldn't be collected */
15301562 PyGC_Head unreachable ; /* non-problematic unreachable trash */
15311563 PyGC_Head finalizers ; /* objects with, & reachable from, __del__ */
15321564 PyGC_Head * gc ; /* initialize to prevent a compiler warning */
@@ -1537,17 +1569,23 @@ gc_collect_region(PyThreadState *tstate,
15371569
15381570 validate_list (from , collecting_clear_unreachable_clear );
15391571 validate_list (to , collecting_clear_unreachable_clear );
1572+ validate_consistent_old_space (from );
1573+ validate_consistent_old_space (to );
15401574
1575+ gc_list_init (& unreachable );
15411576 deduce_unreachable (from , & unreachable );
1577+ validate_consistent_old_space (from );
15421578 if (untrack & UNTRACK_TUPLES ) {
15431579 untrack_tuples (from );
15441580 }
15451581 if (untrack & UNTRACK_DICTS ) {
15461582 untrack_dicts (from );
15471583 }
1584+ validate_consistent_old_space (to );
15481585 if (from != to ) {
15491586 gc_list_merge (from , to );
15501587 }
1588+ validate_consistent_old_space (to );
15511589 validate_old (gcstate );
15521590 /* Move reachable objects to next generation. */
15531591
@@ -1558,11 +1596,14 @@ gc_collect_region(PyThreadState *tstate,
15581596 // NEXT_MASK_UNREACHABLE is cleared here.
15591597 // After move_legacy_finalizers(), unreachable is normal list.
15601598 move_legacy_finalizers (& unreachable , & finalizers );
1599+ validate_consistent_old_space (& unreachable );
1600+ validate_consistent_old_space (& finalizers );
15611601 /* finalizers contains the unreachable objects with a legacy finalizer;
15621602 * unreachable objects reachable *from* those are also uncollectable,
15631603 * and we move those into the finalizers list too.
15641604 */
15651605 move_legacy_finalizer_reachable (& finalizers );
1606+ validate_consistent_old_space (& finalizers );
15661607
15671608 validate_list (& finalizers , collecting_clear_unreachable_clear );
15681609 validate_list (& unreachable , collecting_set_unreachable_clear );
@@ -1575,44 +1616,51 @@ gc_collect_region(PyThreadState *tstate,
15751616 }
15761617
15771618 /* Clear weakrefs and invoke callbacks as necessary. */
1578- m += handle_weakrefs (& unreachable , to );
1619+ stats -> collected += handle_weakrefs (& unreachable , to );
1620+ validate_consistent_old_space (to );
15791621
15801622 validate_list (to , collecting_clear_unreachable_clear );
15811623 validate_list (& unreachable , collecting_set_unreachable_clear );
15821624
15831625 /* Call tp_finalize on objects which have one. */
15841626 finalize_garbage (tstate , & unreachable );
1627+ validate_consistent_old_space (& unreachable );
15851628
15861629 /* Handle any objects that may have resurrected after the call
15871630 * to 'finalize_garbage' and continue the collection with the
15881631 * objects that are still unreachable */
15891632 PyGC_Head final_unreachable ;
1633+ gc_list_init (& final_unreachable );
1634+ validate_consistent_old_space (to );
15901635 handle_resurrected_objects (& unreachable , & final_unreachable , to );
1636+ validate_consistent_old_space (to );
15911637
15921638 /* Call tp_clear on objects in the final_unreachable set. This will cause
15931639 * the reference cycles to be broken. It may also cause some objects
15941640 * in finalizers to be freed.
15951641 */
1596- m += gc_list_size (& final_unreachable );
1642+ stats -> collected += gc_list_size (& final_unreachable );
15971643 delete_garbage (tstate , gcstate , & final_unreachable , to );
1644+ validate_consistent_old_space (to );
15981645
15991646 /* Collect statistics on uncollectable objects found and print
16001647 * debugging information. */
1648+ Py_ssize_t n = 0 ;
16011649 for (gc = GC_NEXT (& finalizers ); gc != & finalizers ; gc = GC_NEXT (gc )) {
16021650 n ++ ;
16031651 if (gcstate -> debug & DEBUG_UNCOLLECTABLE )
16041652 debug_cycle ("uncollectable" , FROM_GC (gc ));
16051653 }
16061654
1655+ stats -> uncollectable = n ;
16071656 /* Append instances in the uncollectable set to a Python
16081657 * reachable list of garbage. The programmer has to deal with
16091658 * this if they insist on creating this type of structure.
16101659 */
16111660 handle_legacy_finalizers (tstate , gcstate , & finalizers , to );
16121661 validate_list (to , collecting_clear_unreachable_clear );
1662+ validate_consistent_old_space (to );
16131663
1614- stats -> collected = m ;
1615- stats -> uncollectable = n ;
16161664 validate_old (gcstate );
16171665}
16181666
0 commit comments