@@ -99,13 +99,40 @@ pub(super) unsafe fn try_trace_obj<T: PyPayload>(x: &PyObject, tracer_fn: &mut T
9999 payload. try_traverse ( tracer_fn)
100100}
101101
102+ bitflags:: bitflags! {
103+ /// GC bits for free-threading support (like CPython's ob_gc_bits in Py_GIL_DISABLED)
104+ /// These bits are stored in a separate atomic field for lock-free access.
105+ /// See CPython's Include/internal/pycore_gc.h
106+ #[ derive( Copy , Clone , Debug , Default ) ]
107+ pub struct GcBits : u8 {
108+ /// Tracked by the GC
109+ const TRACKED = 1 << 0 ;
110+ /// tp_finalize was called (prevents __del__ from being called twice)
111+ const FINALIZED = 1 << 1 ;
112+ /// Object is unreachable (during GC collection)
113+ const UNREACHABLE = 1 << 2 ;
114+ /// Object is frozen (immutable)
115+ const FROZEN = 1 << 3 ;
116+ /// Memory the object references is shared between multiple threads
117+ /// and needs special handling when freeing due to possible in-flight lock-free reads
118+ const SHARED = 1 << 4 ;
119+ /// Memory of the object itself is shared between multiple threads
120+ /// Objects with this bit that are GC objects will automatically be delay-freed
121+ const SHARED_INLINE = 1 << 5 ;
122+ /// Use deferred reference counting
123+ const DEFERRED = 1 << 6 ;
124+ }
125+ }
126+
102127/// This is an actual python object. It consists of a `typ` which is the
103128/// python class, and carries some rust payload optionally. This rust
104129/// payload can be a rust float or rust int in case of float and int objects.
105130#[ repr( C ) ]
106131pub ( super ) struct PyInner < T > {
107132 pub ( super ) ref_count : RefCount ,
108133 pub ( super ) vtable : & ' static PyObjVTable ,
134+ /// GC bits for free-threading (like ob_gc_bits)
135+ pub ( super ) gc_bits : PyAtomic < u8 > ,
109136
110137 pub ( super ) typ : PyAtomicRef < PyType > , // __class__ member
111138 pub ( super ) dict : Option < InstanceDict > ,
@@ -448,6 +475,7 @@ impl<T: PyPayload + core::fmt::Debug> PyInner<T> {
448475 Box :: new ( Self {
449476 ref_count : RefCount :: new ( ) ,
450477 vtable : PyObjVTable :: of :: < T > ( ) ,
478+ gc_bits : Radium :: new ( 0 ) ,
451479 typ : PyAtomicRef :: from ( typ) ,
452480 dict : dict. map ( InstanceDict :: new) ,
453481 weak_list : WeakRefList :: new ( ) ,
@@ -780,6 +808,25 @@ impl PyObject {
780808 self
781809 }
782810
811+ /// Check if the object has been finalized (__del__ already called).
812+ /// Like CPython's _PyGC_FINALIZED in Py_GIL_DISABLED mode.
813+ #[ inline]
814+ fn gc_finalized ( & self ) -> bool {
815+ use core:: sync:: atomic:: Ordering :: Relaxed ;
816+ GcBits :: from_bits_retain ( self . 0 . gc_bits . load ( Relaxed ) ) . contains ( GcBits :: FINALIZED )
817+ }
818+
819+ /// Mark the object as finalized. Should be called before __del__.
820+ /// Like CPython's _PyGC_SET_FINALIZED in Py_GIL_DISABLED mode.
821+ #[ inline]
822+ fn set_gc_finalized ( & self ) {
823+ use core:: sync:: atomic:: Ordering :: Relaxed ;
824+ let bits = GcBits :: from_bits_retain ( self . 0 . gc_bits . load ( Relaxed ) ) ;
825+ self . 0
826+ . gc_bits
827+ . store ( ( bits | GcBits :: FINALIZED ) . bits ( ) , Relaxed ) ;
828+ }
829+
783830 #[ inline( always) ] // the outer function is never inlined
784831 fn drop_slow_inner ( & self ) -> Result < ( ) , ( ) > {
785832 // __del__ is mostly not implemented
@@ -810,8 +857,12 @@ impl PyObject {
810857 }
811858
812859 // CPython-compatible drop implementation
860+ // __del__ should only be called once (like CPython's _PyGC_FINALIZED check in GIL_DISABLED)
813861 let del = self . class ( ) . slots . del . load ( ) ;
814- if let Some ( slot_del) = del {
862+ if let Some ( slot_del) = del
863+ && !self . gc_finalized ( )
864+ {
865+ self . set_gc_finalized ( ) ;
815866 call_slot_del ( self , slot_del) ?;
816867 }
817868 if let Some ( wrl) = self . weak_ref_list ( ) {
@@ -1274,6 +1325,7 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) {
12741325 PyInner :: <PyType > {
12751326 ref_count: RefCount :: new( ) ,
12761327 vtable: PyObjVTable :: of:: <PyType >( ) ,
1328+ gc_bits: Radium :: new( 0 ) ,
12771329 dict: None ,
12781330 weak_list: WeakRefList :: new( ) ,
12791331 payload: type_payload,
@@ -1285,6 +1337,7 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) {
12851337 PyInner :: <PyType > {
12861338 ref_count: RefCount :: new( ) ,
12871339 vtable: PyObjVTable :: of:: <PyType >( ) ,
1340+ gc_bits: Radium :: new( 0 ) ,
12881341 dict: None ,
12891342 weak_list: WeakRefList :: new( ) ,
12901343 payload: object_payload,
0 commit comments