Skip to main content

rustpython_vm/object/
traverse_object.rs

1use alloc::fmt;
2use core::any::TypeId;
3
4use crate::{
5    PyObject, PyObjectRef,
6    object::{
7        Erased, InstanceDict, MaybeTraverse, Py, PyObjectPayload, debug_obj, default_dealloc,
8        try_clear_obj, try_traverse_obj,
9    },
10};
11
12use super::{Traverse, TraverseFn};
13
14pub(in crate::object) struct PyObjVTable {
15    pub(in crate::object) typeid: TypeId,
16    /// dealloc: handles __del__, weakref clearing, and memory free.
17    pub(in crate::object) dealloc: unsafe fn(*mut PyObject),
18    pub(in crate::object) debug: unsafe fn(&PyObject, &mut fmt::Formatter<'_>) -> fmt::Result,
19    pub(in crate::object) trace: Option<unsafe fn(&PyObject, &mut TraverseFn<'_>)>,
20    /// Clear for circular reference resolution (tp_clear).
21    /// Called just before deallocation to extract child references.
22    pub(in crate::object) clear: Option<unsafe fn(*mut PyObject, &mut Vec<PyObjectRef>)>,
23}
24
25impl PyObjVTable {
26    pub(super) const fn of<T: PyObjectPayload>() -> &'static Self {
27        &Self {
28            typeid: T::PAYLOAD_TYPE_ID,
29            dealloc: default_dealloc::<T>,
30            debug: debug_obj::<T>,
31            trace: const {
32                if T::HAS_TRAVERSE {
33                    Some(try_traverse_obj::<T>)
34                } else {
35                    None
36                }
37            },
38            clear: const {
39                if T::HAS_CLEAR {
40                    Some(try_clear_obj::<T>)
41                } else {
42                    None
43                }
44            },
45        }
46    }
47}
48
49unsafe impl Traverse for InstanceDict {
50    fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
51        self.dict.traverse(tracer_fn)
52    }
53}
54
55unsafe impl Traverse for Py<Erased> {
56    /// Because PyObject hold a `Py<Erased>`, so we need to trace it
57    fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
58        // For heap type instances, traverse the type reference.
59        // PyAtomicRef holds a strong reference (via PyRef::leak), so GC must
60        // account for it to correctly detect instance ↔ type cycles.
61        // Static types are always alive and don't need this.
62        let typ = &*self.typ;
63        if typ.heaptype_ext.is_some() {
64            // Safety: Py<PyType> and PyObject share the same memory layout
65            let typ_obj: &PyObject = unsafe { &*(typ as *const _ as *const PyObject) };
66            tracer_fn(typ_obj);
67        }
68        // Traverse ObjExt prefix fields (dict and slots) if present
69        if let Some(ext) = self.ext_ref() {
70            ext.dict.traverse(tracer_fn);
71            for slot in self.slot_cells() {
72                slot.traverse(tracer_fn);
73            }
74        }
75
76        if let Some(f) = self.vtable.trace {
77            unsafe {
78                let zelf = &*(self as *const Self as *const PyObject);
79                f(zelf, tracer_fn)
80            }
81        };
82    }
83}
84
85unsafe impl<T: MaybeTraverse> Traverse for Py<T> {
86    /// DO notice that call `trace` on `Py<T>` means apply `tracer_fn` on `Py<T>`'s children,
87    /// not like call `trace` on `PyRef<T>` which apply `tracer_fn` on `PyRef<T>` itself
88    ///
89    /// Type is known, so we can call `try_trace` directly instead of using erased type vtable
90    fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
91        // For heap type instances, traverse the type reference (same as erased version)
92        let typ = &*self.typ;
93        if typ.heaptype_ext.is_some() {
94            let typ_obj: &PyObject = unsafe { &*(typ as *const _ as *const PyObject) };
95            tracer_fn(typ_obj);
96        }
97        // Traverse ObjExt prefix fields (dict and slots) if present
98        if let Some(ext) = self.ext_ref() {
99            ext.dict.traverse(tracer_fn);
100            for slot in self.slot_cells() {
101                slot.traverse(tracer_fn);
102            }
103        }
104        T::try_traverse(&self.payload, tracer_fn);
105    }
106}