2222
2323typedef struct _gc_runtime_state GCState ;
2424
25+ /* Root marking requires unique interpreter ownership. Keep this state outside
26+ * _PyRuntime so secondary interpreters and runtime reinitialization disable it.
27+ */
28+ static int gc_ownership_state ; // 0: never initialized, 1: unique, 2: unknown
29+
2530#ifdef Py_DEBUG
2631# define GC_DEBUG
2732#endif
@@ -82,10 +87,10 @@ gc_set_refs(PyGC_Head *g, Py_ssize_t refs)
8287}
8388
8489static inline void
85- gc_reset_refs (PyGC_Head * g , Py_ssize_t refs )
90+ gc_reset_refs (PyGC_Head * g , Py_ssize_t refs , uintptr_t epoch )
8691{
8792 g -> _gc_prev = (g -> _gc_prev & _PyGC_PREV_MASK_FINALIZED )
88- | PREV_MASK_COLLECTING
93+ | epoch | PREV_MASK_COLLECTING
8994 | ((uintptr_t )(refs ) << _PyGC_PREV_SHIFT );
9095}
9196
@@ -113,6 +118,10 @@ get_gc_state(void)
113118void
114119_PyGC_InitState (GCState * gcstate )
115120{
121+ int expected = 0 ;
122+ if (!_Py_atomic_compare_exchange_int (& gc_ownership_state , & expected , 1 )) {
123+ _Py_atomic_store_int_relaxed (& gc_ownership_state , 2 );
124+ }
116125#define INIT_HEAD (GEN ) \
117126 do { \
118127 GEN.head._gc_next = (uintptr_t)&GEN.head; \
@@ -124,6 +133,7 @@ _PyGC_InitState(GCState *gcstate)
124133 INIT_HEAD (gcstate -> generations [i ]);
125134 };
126135 gcstate -> generation0 = GEN_HEAD (gcstate , 0 );
136+ gcstate -> mark_epoch = 0 ;
127137 INIT_HEAD (gcstate -> permanent_generation );
128138
129139#undef INIT_HEAD
@@ -160,7 +170,7 @@ _gc_prev values
160170
161171Between collections, _gc_prev is used for doubly linked list.
162172
163- Lowest two bits of _gc_prev are used for flags.
173+ Low bits of _gc_prev are used for flags, including a mark epoch on supported ABIs .
164174PREV_MASK_COLLECTING is used only while collecting and cleared before GC ends
165175or _PyObject_GC_UNTRACK() is called.
166176
@@ -400,6 +410,7 @@ update_refs(PyGC_Head *containers)
400410 PyGC_Head * next ;
401411 PyGC_Head * gc = GC_NEXT (containers );
402412 Py_ssize_t candidates = 0 ;
413+ uintptr_t epoch = _PyGC_PREV_MASK_EPOCH ? get_gc_state ()-> mark_epoch : 0 ;
403414
404415 while (gc != containers ) {
405416 next = GC_NEXT (gc );
@@ -410,7 +421,7 @@ update_refs(PyGC_Head *containers)
410421 gc = next ;
411422 continue ;
412423 }
413- gc_reset_refs (gc , Py_REFCNT (op ));
424+ gc_reset_refs (gc , Py_REFCNT (op ), epoch );
414425 /* Python's cyclic gc should never see an incoming refcount
415426 * of 0: if something decref'ed to 0, it should have been
416427 * deallocated immediately at that time.
@@ -1227,6 +1238,98 @@ deduce_unreachable(PyGC_Head *base, PyGC_Head *unreachable) {
12271238 return candidates ;
12281239}
12291240
1241+ typedef struct {
1242+ PyGC_Head live ;
1243+ uintptr_t epoch ;
1244+ Py_ssize_t candidates ;
1245+ } GCMarkQueue ;
1246+
1247+ static int
1248+ visit_add_to_container (PyObject * op , void * arg )
1249+ {
1250+ OBJECT_STAT_INC (object_visits );
1251+ if (!_Py_IsImmortal (op ) && _PyObject_IS_GC (op ) &&
1252+ _PyObject_GC_IS_TRACKED (op )) {
1253+ PyGC_Head * gc = AS_GC (op );
1254+ GCMarkQueue * queue = arg ;
1255+ /* Recheck ownership after tp_is_gc, which can create an interpreter.
1256+ * A partial live set is safe: its outgoing references stay external.
1257+ */
1258+ if ((gc -> _gc_prev & _PyGC_PREV_MASK_EPOCH ) != queue -> epoch &&
1259+ _Py_atomic_load_int_relaxed (& gc_ownership_state ) == 1 ) {
1260+ gc -> _gc_prev = (gc -> _gc_prev & ~_PyGC_PREV_MASK_EPOCH )
1261+ | queue -> epoch ;
1262+ gc_list_move (gc , & queue -> live );
1263+ queue -> candidates ++ ;
1264+ }
1265+ }
1266+ return 0 ;
1267+ }
1268+
1269+ /* Avoid extension callbacks while root enumeration holds HEAD_LOCK. */
1270+ static int
1271+ visit_known_root (PyObject * op , void * arg )
1272+ {
1273+ if (op == NULL ) {
1274+ return 0 ;
1275+ }
1276+ if (Py_TYPE (op )-> tp_is_gc != NULL ) {
1277+ OBJECT_STAT_INC (object_visits );
1278+ return 0 ;
1279+ }
1280+ return visit_add_to_container (op , arg );
1281+ }
1282+
1283+ static void
1284+ mark_known_roots (GCMarkQueue * queue )
1285+ {
1286+ PyInterpreterState * interp = _PyInterpreterState_GET ();
1287+ visit_known_root (interp -> sysdict , queue );
1288+ visit_known_root (interp -> builtins , queue );
1289+ visit_known_root (interp -> dict , queue );
1290+ visit_known_root (interp -> imports .modules , queue );
1291+ _Py_FOR_EACH_TSTATE_BEGIN (interp , tstate ) {
1292+ for (_PyInterpreterFrame * frame = tstate -> current_frame ;
1293+ frame != NULL ; frame = frame -> previous ) {
1294+ if (frame -> owner >= FRAME_OWNED_BY_INTERPRETER ||
1295+ frame -> stackpointer == NULL || _PyFrame_IsIncomplete (frame )) {
1296+ continue ;
1297+ }
1298+ (void )_PyFrame_Traverse (frame , visit_known_root , queue );
1299+ }
1300+ }
1301+ _Py_FOR_EACH_TSTATE_END (interp );
1302+ PyGC_Head * gc = GC_NEXT (& queue -> live );
1303+ while (gc != & queue -> live ) {
1304+ PyObject * op = FROM_GC (gc );
1305+ (void )Py_TYPE (op )-> tp_traverse (op , visit_add_to_container , queue );
1306+ /* Traversal can append objects, so read next afterward. */
1307+ gc = GC_NEXT (gc );
1308+ }
1309+ }
1310+
1311+ /* Toggle the epoch to distinguish unseen objects. GC_TRACK stamps new objects
1312+ * with the new epoch. The live list doubles as the pending work queue.
1313+ */
1314+ static Py_ssize_t
1315+ collect_from_known_roots (PyGC_Head * base , PyGC_Head * unreachable )
1316+ {
1317+ GCState * gcstate = get_gc_state ();
1318+ gcstate -> mark_epoch ^= _PyGC_PREV_MASK_EPOCH ;
1319+ GCMarkQueue queue = {.epoch = gcstate -> mark_epoch };
1320+ gc_list_init (& queue .live );
1321+ mark_known_roots (& queue );
1322+
1323+ /* Known-live references stay external to the residual analysis.
1324+ * update_refs stamps the remaining candidates before callbacks can run.
1325+ */
1326+ Py_ssize_t candidates = deduce_unreachable (base , unreachable );
1327+ gc_list_merge (base , & queue .live );
1328+ gc_list_merge (& queue .live , base );
1329+ validate_list (base , collecting_clear_unreachable_clear );
1330+ return queue .candidates + candidates ;
1331+ }
1332+
12301333/* Handle objects that may have resurrected after a call to 'finalize_garbage', moving
12311334 them to 'old_generation' and placing the rest on 'still_unreachable'.
12321335
@@ -1521,7 +1624,19 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason)
15211624 }
15221625 validate_list (old , collecting_clear_unreachable_clear );
15231626
1524- stats .candidates = deduce_unreachable (young , & unreachable );
1627+ if (_PyGC_PREV_MASK_EPOCH && generation == NUM_GENERATIONS - 1 &&
1628+ _Py_IsMainInterpreter (tstate -> interp ) &&
1629+ gcstate -> heap_size >= 4096 &&
1630+ gc_list_is_empty (& gcstate -> permanent_generation .head ) &&
1631+ _Py_atomic_load_int_relaxed (& gc_ownership_state ) == 1 ) {
1632+ /* Without frozen or foreign objects, every tracked non-immortal
1633+ * object belongs to this full collection before update_refs.
1634+ */
1635+ stats .candidates = collect_from_known_roots (young , & unreachable );
1636+ }
1637+ else {
1638+ stats .candidates = deduce_unreachable (young , & unreachable );
1639+ }
15251640
15261641 untrack_tuples (young );
15271642 /* Move reachable objects to next generation. */
@@ -1901,6 +2016,7 @@ finalize_unlink_gc_head(PyGC_Head *gc) {
19012016void
19022017_PyGC_Fini (PyInterpreterState * interp )
19032018{
2019+ _Py_atomic_store_int_relaxed (& gc_ownership_state , 2 );
19042020 GCState * gcstate = & interp -> gc ;
19052021 Py_CLEAR (gcstate -> garbage );
19062022 Py_CLEAR (gcstate -> callbacks );
0 commit comments