@@ -1320,6 +1320,7 @@ gc_collect_young(PyThreadState *tstate,
13201320
13211321    PyGC_Head  survivors ;
13221322    gc_list_init (& survivors );
1323+     gc_list_set_space (young , gcstate -> visited_space );
13231324    gc_collect_region (tstate , young , & survivors , UNTRACK_TUPLES , stats );
13241325    Py_ssize_t  survivor_count  =  0 ;
13251326    if  (gcstate -> visited_space ) {
@@ -1407,6 +1408,7 @@ expand_region_transitively_reachable(PyGC_Head *container, PyGC_Head *gc, GCStat
14071408         * have been marked as visited */ 
14081409        assert (IS_IN_VISITED (gc , gcstate -> visited_space ));
14091410        PyObject  * op  =  FROM_GC (gc );
1411+         assert (_PyObject_GC_IS_TRACKED (op ));
14101412        if  (_Py_IsImmortal (op )) {
14111413            PyGC_Head  * next  =  GC_NEXT (gc );
14121414            gc_list_move (gc , & get_gc_state ()-> permanent_generation .head );
@@ -1426,97 +1428,137 @@ expand_region_transitively_reachable(PyGC_Head *container, PyGC_Head *gc, GCStat
14261428static  void 
14271429completed_cycle (GCState  * gcstate )
14281430{
1429- #ifdef  Py_DEBUG 
1430-     PyGC_Head  * not_visited  =  & gcstate -> old [gcstate -> visited_space ^1 ].head ;
1431-     assert (gc_list_is_empty (not_visited ));
1432- #endif 
1433-     gcstate -> visited_space  =  flip_old_space (gcstate -> visited_space );
1431+     assert (gc_list_is_empty (& gcstate -> old [gcstate -> visited_space ^1 ].head ));
1432+     int  not_visited  =  gcstate -> visited_space ;
1433+     gcstate -> visited_space  =  flip_old_space (not_visited );
14341434    /* Make sure all young objects have old space bit set correctly */ 
14351435    PyGC_Head  * young  =  & gcstate -> young .head ;
14361436    PyGC_Head  * gc  =  GC_NEXT (young );
14371437    while  (gc  !=  young ) {
14381438        PyGC_Head  * next  =  GC_NEXT (gc );
1439-         gc_set_old_space (gc , gcstate -> visited_space );
1439+         gc_set_old_space (gc , not_visited );
14401440        gc  =  next ;
14411441    }
14421442    gcstate -> work_to_do  =  0 ;
14431443    gcstate -> phase  =  GC_PHASE_MARK ;
14441444}
14451445
1446- static  void 
1447- gc_mark (PyThreadState  * tstate , struct  gc_collection_stats  * stats )
1446+ static  int 
1447+ move_to_reachable (PyObject  * op , PyGC_Head  * reachable , int  visited_space )
1448+ {
1449+     if  (op  !=  NULL  &&  !_Py_IsImmortal (op ) &&  _PyObject_IS_GC (op )) {
1450+         PyGC_Head  * gc  =  AS_GC (op );
1451+         if  (_PyObject_GC_IS_TRACKED (op ) && 
1452+             gc_old_space (gc ) !=  visited_space ) {
1453+             gc_flip_old_space (gc );
1454+             gc_list_move (gc , reachable );
1455+             return  1 ;
1456+         }
1457+     }
1458+     return  0 ;
1459+ }
1460+ 
1461+ static  int 
1462+ mark_all_reachable (PyGC_Head  * reachable , PyGC_Head  * visited , int  visited_space )
1463+ {
1464+     // Transitively traverse all objects from reachable, until empty 
1465+     struct  container_and_flag  arg  =  {
1466+         .container  =  reachable ,
1467+         .visited_space  =  visited_space ,
1468+         .mark  =  1 ,
1469+         .size  =  0 
1470+     };
1471+     while  (!gc_list_is_empty (reachable )) {
1472+         PyGC_Head  * gc  =  _PyGCHead_NEXT (reachable );
1473+         assert (gc_old_space (gc ) ==  visited_space );
1474+         gc_list_move (gc , visited );
1475+         PyObject  * op  =  FROM_GC (gc );
1476+         traverseproc  traverse  =  Py_TYPE (op )-> tp_traverse ;
1477+         (void ) traverse (op ,
1478+                         visit_add_to_container ,
1479+                         & arg );
1480+     }
1481+     gc_list_validate_space (visited , visited_space );
1482+     return  arg .size ;
1483+ }
1484+ 
1485+ static  int 
1486+ mark_global_roots (PyInterpreterState  * interp , PyGC_Head  * visited , int  visited_space )
1487+ {
1488+     PyGC_Head  reachable ;
1489+     gc_list_init (& reachable );
1490+     Py_ssize_t  objects_marked  =  0 ;
1491+     objects_marked  +=  move_to_reachable (interp -> sysdict , & reachable , visited_space );
1492+     objects_marked  +=  move_to_reachable (interp -> builtins , & reachable , visited_space );
1493+     objects_marked  +=  move_to_reachable (interp -> dict , & reachable , visited_space );
1494+     objects_marked  +=  mark_all_reachable (& reachable , visited , visited_space );
1495+     assert (gc_list_is_empty (& reachable ));
1496+     return  objects_marked ;
1497+ }
1498+ 
1499+ static  int 
1500+ mark_stacks (PyInterpreterState  * interp , PyGC_Head  * visited , int  visited_space , bool  start )
14481501{
1449-     // TO DO -- Make this incremental 
1450-     GCState  * gcstate  =  & tstate -> interp -> gc ;
1451-     validate_old (gcstate );
1452-     PyGC_Head  * visited  =  & gcstate -> old [gcstate -> visited_space ].head ;
14531502    PyGC_Head  reachable ;
14541503    gc_list_init (& reachable );
1455-     // Move all reachable objects into visited space. 
1456-     PyGC_Head  * gc  =  AS_GC (tstate -> interp -> sysdict );
14571504    Py_ssize_t  objects_marked  =  0 ;
1458-     if  (gc_old_space (gc ) !=  gcstate -> visited_space ) {
1459-         gc_flip_old_space (gc );
1460-         gc_list_move (gc , & reachable );
1461-         objects_marked ++ ;
1462-     }
1463-     gc  =  AS_GC (tstate -> interp -> builtins );
1464-     if  (gc_old_space (gc ) !=  gcstate -> visited_space ) {
1465-         gc_flip_old_space (gc );
1466-         gc_list_move (gc , & reachable );
1467-         objects_marked ++ ;
1468-     }
14691505    // Move all objects on stacks to reachable 
14701506    _PyRuntimeState  * runtime  =  & _PyRuntime ;
14711507    HEAD_LOCK (runtime );
1472-     PyThreadState *  ts  =  PyInterpreterState_ThreadHead (tstate -> interp );
1508+     PyThreadState *  ts  =  PyInterpreterState_ThreadHead (interp );
14731509    HEAD_UNLOCK (runtime );
14741510    while  (ts ) {
14751511        _PyInterpreterFrame  * frame  =  ts -> current_frame ;
14761512        while  (frame ) {
14771513            _PyStackRef  * locals  =  frame -> localsplus ;
14781514            _PyStackRef  * sp  =  frame -> stackpointer ;
1515+             if  (frame -> owner  !=  FRAME_OWNED_BY_CSTACK ) {
1516+                 objects_marked  +=  move_to_reachable (frame -> f_locals , & reachable , visited_space );
1517+                 PyObject  * func  =  PyStackRef_AsPyObjectBorrow (frame -> f_funcobj );
1518+                 objects_marked  +=  move_to_reachable (func , & reachable , visited_space );
1519+             }
14791520            while  (sp  >  locals ) {
14801521                sp -- ;
14811522                if  (PyStackRef_IsNull (* sp )) {
14821523                    continue ;
14831524                }
14841525                PyObject  * op  =  PyStackRef_AsPyObjectBorrow (* sp );
14851526                if  (!_Py_IsImmortal (op ) &&  _PyObject_IS_GC (op )) {
1486-                    PyGC_Head  * gc  =  AS_GC (op );
1527+                      PyGC_Head  * gc  =  AS_GC (op );
14871528                    if  (_PyObject_GC_IS_TRACKED (op ) && 
1488-                         gc_old_space (gc ) !=  gcstate -> visited_space ) {
1529+                         gc_old_space (gc ) !=  visited_space ) {
14891530                        gc_flip_old_space (gc );
14901531                        objects_marked ++ ;
14911532                        gc_list_move (gc , & reachable );
14921533                    }
14931534                }
14941535            }
1536+             if  (!start  &&  frame -> visited ) {
1537+                 // If this frame has already been visited, then the lower frames 
1538+                 // will have already been visited and will not have changed 
1539+                 break ;
1540+             }
1541+             frame -> visited  =  1 ;
14951542            frame  =  frame -> previous ;
14961543        }
14971544        HEAD_LOCK (runtime );
14981545        ts  =  PyThreadState_Next (ts );
14991546        HEAD_UNLOCK (runtime );
15001547    }
1501-     // Transitively traverse all objects from reachable, until empty 
1502-     struct  container_and_flag  arg  =  {
1503-         .container  =  & reachable ,
1504-         .visited_space  =  gcstate -> visited_space ,
1505-         .mark  =  1 ,
1506-         .size  =  0 
1507-     };
1508-     while  (!gc_list_is_empty (& reachable )) {
1509-         PyGC_Head  * gc  =  _PyGCHead_NEXT (& reachable );
1510-         assert (gc_old_space (gc ) ==  gcstate -> visited_space );
1511-         gc_list_move (gc , visited );
1512-         PyObject  * op  =  FROM_GC (gc );
1513-         traverseproc  traverse  =  Py_TYPE (op )-> tp_traverse ;
1514-         (void ) traverse (op ,
1515-                         visit_add_to_container ,
1516-                         & arg );
1517-     }
1518-     objects_marked  +=  arg .size ;
1548+     objects_marked  +=  mark_all_reachable (& reachable , visited , visited_space );
1549+     assert (gc_list_is_empty (& reachable ));
1550+     return  objects_marked ;
1551+ }
1552+ 
1553+ static  void 
1554+ mark_at_start (PyThreadState  * tstate )
1555+ {
1556+     // TO DO -- Make this incremental 
1557+     GCState  * gcstate  =  & tstate -> interp -> gc ;
15191558    validate_old (gcstate );
1559+     PyGC_Head  * visited  =  & gcstate -> old [gcstate -> visited_space ].head ;
1560+     Py_ssize_t  objects_marked  =  mark_global_roots (tstate -> interp , visited , gcstate -> visited_space );
1561+     objects_marked  +=  mark_stacks (tstate -> interp , visited , gcstate -> visited_space , true);
15201562    gcstate -> work_to_do  -=  objects_marked ;
15211563#ifdef  Py_STATS 
15221564    if  (_Py_stats ) {
@@ -1531,8 +1573,9 @@ gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats)
15311573{
15321574    GC_STAT_ADD (1 , collections , 1 );
15331575    GCState  * gcstate  =  & tstate -> interp -> gc ;
1576+     gcstate -> work_to_do  +=  gcstate -> young .count ;
15341577    if  (gcstate -> phase  ==  GC_PHASE_MARK ) {
1535-         gc_mark (tstate ,  stats );
1578+         mark_at_start (tstate );
15361579        return ;
15371580    }
15381581    PyGC_Head  * not_visited  =  & gcstate -> old [gcstate -> visited_space ^1 ].head ;
@@ -1543,6 +1586,8 @@ gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats)
15431586    if  (scale_factor  <  1 ) {
15441587        scale_factor  =  1 ;
15451588    }
1589+     gcstate -> work_to_do  -=  mark_stacks (tstate -> interp , visited , gcstate -> visited_space , false);
1590+     gc_list_set_space (& gcstate -> young .head , gcstate -> visited_space );
15461591    gc_list_merge (& gcstate -> young .head , & increment );
15471592    gcstate -> young .count  =  0 ;
15481593    gc_list_validate_space (& increment , gcstate -> visited_space );
@@ -1554,6 +1599,7 @@ gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats)
15541599        PyGC_Head  * gc  =  _PyGCHead_NEXT (not_visited );
15551600        gc_list_move (gc , & increment );
15561601        increment_size ++ ;
1602+         assert (!_Py_IsImmortal (FROM_GC (gc )));
15571603        gc_set_old_space (gc , gcstate -> visited_space );
15581604        increment_size  +=  expand_region_transitively_reachable (& increment , gc , gcstate );
15591605    }
@@ -1564,7 +1610,7 @@ gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats)
15641610    gc_list_validate_space (& survivors , gcstate -> visited_space );
15651611    gc_list_merge (& survivors , visited );
15661612    assert (gc_list_is_empty (& increment ));
1567-     gcstate -> work_to_do  +=  gcstate -> heap_size  / SCAN_RATE_DIVISOR  / scale_factor ;
1613+     gcstate -> work_to_do  +=  gcstate -> heap_size  / SCAN_RATE_DIVISOR /  2  / scale_factor ;
15681614    gcstate -> work_to_do  -=  increment_size ;
15691615
15701616    validate_old (gcstate );
@@ -1585,20 +1631,25 @@ gc_collect_full(PyThreadState *tstate,
15851631    PyGC_Head  * young  =  & gcstate -> young .head ;
15861632    PyGC_Head  * pending  =  & gcstate -> old [gcstate -> visited_space ^1 ].head ;
15871633    PyGC_Head  * visited  =  & gcstate -> old [gcstate -> visited_space ].head ;
1588-     /* merge all generations into visited */ 
1589-     gc_list_validate_space (young , gcstate -> visited_space );
1590-     gc_list_set_space (pending , gcstate -> visited_space );
1634+     mark_global_roots (tstate -> interp , visited , gcstate -> visited_space );
1635+     mark_stacks (tstate -> interp , visited , gcstate -> visited_space , true);
1636+     /* merge all generations into pending */ 
1637+     gc_list_validate_space (young , 1 - gcstate -> visited_space );
15911638    gc_list_merge (young , pending );
1639+     gc_list_set_space (visited , 1 - gcstate -> visited_space );
1640+     gc_list_merge (visited , pending );
1641+     /* Mark reachable */ 
1642+     mark_global_roots (tstate -> interp , visited , gcstate -> visited_space );
1643+     mark_stacks (tstate -> interp , visited , gcstate -> visited_space , true);
15921644    gcstate -> young .count  =  0 ;
1593-     gc_list_merge (pending , visited );
1594- 
1595-     gc_collect_region (tstate , visited , visited ,
1645+     gc_list_set_space (pending , gcstate -> visited_space );
1646+     gc_collect_region (tstate , pending , visited ,
15961647                      UNTRACK_TUPLES  | UNTRACK_DICTS ,
15971648                      stats );
15981649    gcstate -> young .count  =  0 ;
15991650    gcstate -> old [0 ].count  =  0 ;
16001651    gcstate -> old [1 ].count  =  0 ;
1601- 
1652+      completed_cycle ( gcstate ); 
16021653    gcstate -> work_to_do  =  -  gcstate -> young .threshold  *  2 ;
16031654    _PyGC_ClearAllFreeLists (tstate -> interp );
16041655    validate_old (gcstate );
0 commit comments