Skip to content

Commit daec095

Browse files
committed
impl gc finialized
1 parent 0cd6eb7 commit daec095

File tree

1 file changed

+54
-1
lines changed

1 file changed

+54
-1
lines changed

crates/vm/src/object/core.rs

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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)]
106131
pub(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

Comments
 (0)