@@ -63,6 +63,25 @@ gc_is_collecting(PyGC_Head *g)
6363 return (g -> _gc_prev & PREV_MASK_COLLECTING ) != 0 ;
6464}
6565
66+ #ifdef _PyGC_PREV_MASK_PERM
67+ /* Test whether a GC header retains permanent-generation ownership.
68+ *
69+ * Arguments:
70+ * - `gc`: the object header to inspect.
71+ *
72+ * Return true for permanent objects, including queued weakrefs temporarily
73+ * outside the permanent list.
74+ */
75+ static inline bool
76+ gc_is_permanent (PyGC_Head * gc )
77+ {
78+ static_assert (_Alignof(PyGC_Head ) >= (1 << _PyGC_PREV_SHIFT ), "GC headers must satisfy their reserved-bit alignment" );
79+ static_assert (_Alignof(PyObject ) >= (1 << _PyGC_PREV_SHIFT ), "GC objects must satisfy their reserved-bit alignment" );
80+
81+ return (gc -> _gc_prev & _PyGC_PREV_MASK_PERM ) != 0 ;
82+ }
83+ #endif
84+
6685static inline void
6786gc_clear_collecting (PyGC_Head * g )
6887{
@@ -161,9 +180,10 @@ _gc_prev values
161180
162181Between collections, _gc_prev is used for doubly linked list.
163182
164- Lowest two bits of _gc_prev are used for flags.
183+ Lowest bits of _gc_prev are used for flags.
165184PREV_MASK_COLLECTING is used only while collecting and cleared before GC ends
166185or _PyObject_GC_UNTRACK() is called.
186+ _PyGC_PREV_MASK_PERM identifies objects owned by the permanent generation.
167187
168188During a collection, _gc_prev is temporary used for gc_refs, and the gc list
169189is singly linked until _gc_prev is restored.
@@ -464,9 +484,8 @@ visit_decref(PyObject *op, void *parent)
464484
465485/* Subtract a GC reference, initializing and marking the referent on first visit.
466486 *
467- * This visitor is used only when every tracked mortal object it may reach
468- * belongs to the collection. Non-GC, untracked, and immortal referents remain
469- * unchanged.
487+ * Every tracked mortal referent must belong to this collection or be marked permanent.
488+ * Non-GC, untracked, immortal, and permanent referents remain unchanged.
470489 *
471490 * Arguments:
472491 * - `op`: the object referenced by the traversed parent.
@@ -490,6 +509,11 @@ gc_decref_and_mark_collecting(PyObject *op, void *parent)
490509 return 0 ;
491510 }
492511
512+ #ifdef _PyGC_PREV_MASK_PERM
513+ if (gc_is_permanent (gc )) {
514+ return 0 ;
515+ }
516+ #endif
493517 if (!_PyObject_GC_IS_TRACKED (op ) || _Py_IsImmortal (op )) {
494518 return 0 ;
495519 }
@@ -560,7 +584,7 @@ subtract_refs(PyGC_Head *containers)
560584 * Prerequisites:
561585 * - This is the initial pass of a full collection, not a resurrection recheck.
562586 * - `containers` is a valid list containing all ordinary generations with no masks set.
563- * - The permanent generation is empty.
587+ * - Permanent objects are identifiable, or the permanent generation is empty.
564588 * - `_PyInterpreterState_MayHaveForeignGcObjects()` is false.
565589 *
566590 * Arguments:
@@ -1086,7 +1110,10 @@ invoke_weakref_callbacks(PyGC_Head *wrcb_to_call, PyGC_Head *old)
10861110 */
10871111 Py_DECREF (op );
10881112 if (wrcb_to_call -> _gc_next == (uintptr_t )gc ) {
1089- /* object is still alive -- move it */
1113+ #ifdef _PyGC_PREV_MASK_PERM
1114+ // The old generation must not retain permanent markers.
1115+ gc -> _gc_prev &= ~_PyGC_PREV_MASK_PERM ;
1116+ #endif
10901117 gc_list_move (gc , old );
10911118 }
10921119 else {
@@ -1350,11 +1377,13 @@ deduce_unreachable(PyGC_Head *base, PyGC_Head *unreachable,
13501377 * refcount greater than 0 when all the references within the
13511378 * set are taken into account).
13521379 */
1353- // Lazy initialization can touch any tracked mortal referent. Use it only
1354- // when all such objects belong to this initial full collection.
1380+ // Marked permanent objects are excluded from lazy initialization.
1381+ // Every other tracked mortal referent must belong to this initial full collection.
13551382 Py_ssize_t candidates ;
13561383 if (full_collection_pass &&
1384+ #ifndef _PyGC_PREV_MASK_PERM
13571385 gc_list_is_empty (& get_gc_state ()-> permanent_generation .head ) &&
1386+ #endif
13581387 !_PyInterpreterState_MayHaveForeignGcObjects ()) {
13591388 candidates = update_and_subtract_refs (base , legacy_finalizer_count_out );
13601389 }
@@ -1986,7 +2015,13 @@ _PyGC_Freeze(PyInterpreterState *interp)
19862015{
19872016 GCState * gcstate = & interp -> gc ;
19882017 for (int i = 0 ; i < NUM_GENERATIONS ; ++ i ) {
1989- gc_list_merge (GEN_HEAD (gcstate , i ), & gcstate -> permanent_generation .head );
2018+ PyGC_Head * generation = GEN_HEAD (gcstate , i );
2019+ #ifdef _PyGC_PREV_MASK_PERM
2020+ for (PyGC_Head * gc = GC_NEXT (generation ); gc != generation ; gc = GC_NEXT (gc )) {
2021+ gc -> _gc_prev |= _PyGC_PREV_MASK_PERM ;
2022+ }
2023+ #endif
2024+ gc_list_merge (generation , & gcstate -> permanent_generation .head );
19902025 gcstate -> generations [i ].count = 0 ;
19912026 }
19922027}
@@ -1995,8 +2030,13 @@ void
19952030_PyGC_Unfreeze (PyInterpreterState * interp )
19962031{
19972032 GCState * gcstate = & interp -> gc ;
1998- gc_list_merge (& gcstate -> permanent_generation .head ,
1999- GEN_HEAD (gcstate , NUM_GENERATIONS - 1 ));
2033+ PyGC_Head * permanent = & gcstate -> permanent_generation .head ;
2034+ #ifdef _PyGC_PREV_MASK_PERM
2035+ for (PyGC_Head * gc = GC_NEXT (permanent ); gc != permanent ; gc = GC_NEXT (gc )) {
2036+ gc -> _gc_prev &= ~_PyGC_PREV_MASK_PERM ;
2037+ }
2038+ #endif
2039+ gc_list_merge (permanent , GEN_HEAD (gcstate , NUM_GENERATIONS - 1 ));
20002040}
20012041
20022042Py_ssize_t
0 commit comments