Skip to content

Commit 9e225f5

Browse files
impl gc finialized (#6689)
* impl gc finialized * Auto-format: cargo fmt --all --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent aa9fc7f commit 9e225f5

File tree

1 file changed

+52
-2
lines changed

1 file changed

+52
-2
lines changed

crates/vm/src/object/core.rs

Lines changed: 52 additions & 2 deletions
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 ob_gc_bits in Py_GIL_DISABLED)
104+
/// These bits are stored in a separate atomic field for lock-free access.
105+
/// See Include/internal/pycore_gc.h
106+
#[derive(Copy, Clone, Debug, Default)]
107+
pub(crate) 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,23 @@ impl PyObject {
780808
self
781809
}
782810

811+
/// Check if the object has been finalized (__del__ already called).
812+
/// _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+
/// _PyGC_SET_FINALIZED in Py_GIL_DISABLED mode.
821+
#[inline]
822+
fn set_gc_finalized(&self) {
823+
use core::sync::atomic::Ordering::Relaxed;
824+
// Atomic RMW to avoid clobbering other concurrent bit updates.
825+
self.0.gc_bits.fetch_or(GcBits::FINALIZED.bits(), Relaxed);
826+
}
827+
783828
#[inline(always)] // the outer function is never inlined
784829
fn drop_slow_inner(&self) -> Result<(), ()> {
785830
// __del__ is mostly not implemented
@@ -809,9 +854,12 @@ impl PyObject {
809854
}
810855
}
811856

812-
// CPython-compatible drop implementation
857+
// __del__ should only be called once (like _PyGC_FINALIZED check in GIL_DISABLED)
813858
let del = self.class().slots.del.load();
814-
if let Some(slot_del) = del {
859+
if let Some(slot_del) = del
860+
&& !self.gc_finalized()
861+
{
862+
self.set_gc_finalized();
815863
call_slot_del(self, slot_del)?;
816864
}
817865
if let Some(wrl) = self.weak_ref_list() {
@@ -1274,6 +1322,7 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) {
12741322
PyInner::<PyType> {
12751323
ref_count: RefCount::new(),
12761324
vtable: PyObjVTable::of::<PyType>(),
1325+
gc_bits: Radium::new(0),
12771326
dict: None,
12781327
weak_list: WeakRefList::new(),
12791328
payload: type_payload,
@@ -1285,6 +1334,7 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) {
12851334
PyInner::<PyType> {
12861335
ref_count: RefCount::new(),
12871336
vtable: PyObjVTable::of::<PyType>(),
1337+
gc_bits: Radium::new(0),
12881338
dict: None,
12891339
weak_list: WeakRefList::new(),
12901340
payload: object_payload,

0 commit comments

Comments
 (0)