Skip to main content

rustpython_vm/object/
ext.rs

1use super::{
2    Traverse, TraverseFn,
3    core::{Py, PyObject, PyObjectRef, PyRef},
4    payload::PyPayload,
5};
6use crate::common::atomic::{Ordering, PyAtomic, Radium};
7use crate::{
8    VirtualMachine,
9    builtins::{PyBaseExceptionRef, PyStrInterned, PyType},
10    convert::{IntoPyException, ToPyObject, ToPyResult, TryFromObject},
11    vm::Context,
12};
13use alloc::fmt;
14
15use core::{
16    borrow::Borrow,
17    marker::PhantomData,
18    ops::Deref,
19    ptr::{NonNull, null_mut},
20};
21
22/* Python objects and references.
23
24Okay, so each python object itself is an class itself (PyObject). Each
25python object can have several references to it (PyObjectRef). These
26references are Rc (reference counting) rust smart pointers. So when
27all references are destroyed, the object itself also can be cleaned up.
28Basically reference counting, but then done by rust.
29
30*/
31
32/*
33 * Good reference: https://github.com/ProgVal/pythonvm-rust/blob/master/src/objects/mod.rs
34 */
35
36/// Use this type for functions which return a python object or an exception.
37/// Both the python object and the python exception are `PyObjectRef` types
38/// since exceptions are also python objects.
39pub type PyResult<T = PyObjectRef> = Result<T, PyBaseExceptionRef>; // A valid value, or an exception
40
41impl<T: PyPayload + fmt::Display> fmt::Display for PyRef<T> {
42    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43        fmt::Display::fmt(&**self, f)
44    }
45}
46
47impl<T: PyPayload + fmt::Display> fmt::Display for Py<T> {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        fmt::Display::fmt(&**self, f)
50    }
51}
52
53#[repr(transparent)]
54pub struct PyExact<T> {
55    inner: Py<T>,
56}
57
58impl<T: PyPayload> PyExact<T> {
59    /// # Safety
60    /// Given reference must be exact type of payload T
61    #[inline(always)]
62    pub const unsafe fn ref_unchecked(r: &Py<T>) -> &Self {
63        unsafe { &*(r as *const _ as *const Self) }
64    }
65}
66
67impl<T: PyPayload> Deref for PyExact<T> {
68    type Target = Py<T>;
69
70    #[inline(always)]
71    fn deref(&self) -> &Py<T> {
72        &self.inner
73    }
74}
75
76impl<T: PyPayload> Borrow<PyObject> for PyExact<T> {
77    #[inline(always)]
78    fn borrow(&self) -> &PyObject {
79        self.inner.borrow()
80    }
81}
82
83impl<T: PyPayload> AsRef<PyObject> for PyExact<T> {
84    #[inline(always)]
85    fn as_ref(&self) -> &PyObject {
86        self.inner.as_ref()
87    }
88}
89
90impl<T: PyPayload> Borrow<Py<T>> for PyExact<T> {
91    #[inline(always)]
92    fn borrow(&self) -> &Py<T> {
93        &self.inner
94    }
95}
96
97impl<T: PyPayload> AsRef<Py<T>> for PyExact<T> {
98    #[inline(always)]
99    fn as_ref(&self) -> &Py<T> {
100        &self.inner
101    }
102}
103
104impl<T: PyPayload> alloc::borrow::ToOwned for PyExact<T> {
105    type Owned = PyRefExact<T>;
106
107    fn to_owned(&self) -> Self::Owned {
108        let owned = self.inner.to_owned();
109        unsafe { PyRefExact::new_unchecked(owned) }
110    }
111}
112
113impl<T: PyPayload> PyRef<T> {
114    pub fn into_exact_or(
115        self,
116        ctx: &Context,
117        f: impl FnOnce(Self) -> PyRefExact<T>,
118    ) -> PyRefExact<T> {
119        if self.class().is(T::class(ctx)) {
120            unsafe { PyRefExact::new_unchecked(self) }
121        } else {
122            f(self)
123        }
124    }
125}
126
127/// PyRef but guaranteed not to be a subtype instance
128#[derive(Debug)]
129#[repr(transparent)]
130pub struct PyRefExact<T: PyPayload> {
131    inner: PyRef<T>,
132}
133
134impl<T: PyPayload> PyRefExact<T> {
135    /// # Safety
136    /// obj must have exact type for the payload
137    #[must_use]
138    pub const unsafe fn new_unchecked(obj: PyRef<T>) -> Self {
139        Self { inner: obj }
140    }
141
142    #[must_use]
143    pub fn into_pyref(self) -> PyRef<T> {
144        self.inner
145    }
146}
147
148impl<T: PyPayload> Clone for PyRefExact<T> {
149    fn clone(&self) -> Self {
150        let inner = self.inner.clone();
151        Self { inner }
152    }
153}
154
155impl<T: PyPayload> TryFromObject for PyRefExact<T> {
156    fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
157        let target_cls = T::class(&vm.ctx);
158        let cls = obj.class();
159        if cls.is(target_cls) {
160            let obj = obj
161                .downcast()
162                .map_err(|obj| vm.new_downcast_runtime_error(target_cls, &obj))?;
163            Ok(Self { inner: obj })
164        } else if cls.fast_issubclass(target_cls) {
165            Err(vm.new_type_error(format!(
166                "Expected an exact instance of '{}', not a subclass '{}'",
167                target_cls.name(),
168                cls.name(),
169            )))
170        } else {
171            Err(vm.new_type_error(format!(
172                "Expected type '{}', not '{}'",
173                target_cls.name(),
174                cls.name(),
175            )))
176        }
177    }
178}
179
180impl<T: PyPayload> Deref for PyRefExact<T> {
181    type Target = PyExact<T>;
182
183    #[inline(always)]
184    fn deref(&self) -> &PyExact<T> {
185        unsafe { PyExact::ref_unchecked(self.inner.deref()) }
186    }
187}
188
189impl<T: PyPayload> Borrow<PyObject> for PyRefExact<T> {
190    #[inline(always)]
191    fn borrow(&self) -> &PyObject {
192        self.inner.borrow()
193    }
194}
195
196impl<T: PyPayload> AsRef<PyObject> for PyRefExact<T> {
197    #[inline(always)]
198    fn as_ref(&self) -> &PyObject {
199        self.inner.as_ref()
200    }
201}
202
203impl<T: PyPayload> Borrow<Py<T>> for PyRefExact<T> {
204    #[inline(always)]
205    fn borrow(&self) -> &Py<T> {
206        self.inner.borrow()
207    }
208}
209
210impl<T: PyPayload> AsRef<Py<T>> for PyRefExact<T> {
211    #[inline(always)]
212    fn as_ref(&self) -> &Py<T> {
213        self.inner.as_ref()
214    }
215}
216
217impl<T: PyPayload> Borrow<PyExact<T>> for PyRefExact<T> {
218    #[inline(always)]
219    fn borrow(&self) -> &PyExact<T> {
220        self
221    }
222}
223
224impl<T: PyPayload> AsRef<PyExact<T>> for PyRefExact<T> {
225    #[inline(always)]
226    fn as_ref(&self) -> &PyExact<T> {
227        self
228    }
229}
230
231impl<T: PyPayload> ToPyObject for PyRefExact<T> {
232    #[inline(always)]
233    fn to_pyobject(self, _vm: &VirtualMachine) -> PyObjectRef {
234        self.inner.into()
235    }
236}
237
238pub struct PyAtomicRef<T> {
239    inner: PyAtomic<*mut u8>,
240    _phantom: PhantomData<T>,
241}
242
243// The cell stores a pointer, not an inline `T`. `PhantomData<T>` would
244// otherwise make `PyAtomicRef<PyObject>` `!Unpin` because `PyObject` is pinned.
245impl<T> Unpin for PyAtomicRef<T> {}
246
247// Typed and untyped cells are the same pointer-sized slot. A typed nullable
248// CAS forwards to the untyped one through this layout.
249const _: () = assert!(
250    core::mem::size_of::<PyAtomicRef<Option<PyObject>>>()
251        == core::mem::size_of::<PyAtomicRef<()>>()
252        && core::mem::align_of::<PyAtomicRef<Option<PyObject>>>()
253            == core::mem::align_of::<PyAtomicRef<()>>()
254        && core::mem::offset_of!(PyAtomicRef<Option<PyObject>>, inner)
255            == core::mem::offset_of!(PyAtomicRef<()>, inner)
256        && core::mem::offset_of!(PyAtomicRef<Option<PyObject>>, inner) == 0
257);
258
259impl<T> Drop for PyAtomicRef<T> {
260    fn drop(&mut self) {
261        // SAFETY: We are dropping the atomic reference, so we can safely
262        // release the pointer.
263        unsafe {
264            let ptr = Radium::swap(&self.inner, null_mut(), Ordering::Relaxed);
265            if let Some(ptr) = NonNull::<PyObject>::new(ptr.cast()) {
266                let _: PyObjectRef = PyObjectRef::from_raw(ptr);
267            }
268        }
269    }
270}
271
272cfg_select! {
273    feature = "threading" => {
274        unsafe impl<T: Send + PyPayload> Send for PyAtomicRef<T> {}
275        unsafe impl<T: Sync + PyPayload> Sync for PyAtomicRef<T> {}
276        unsafe impl<T: Send + PyPayload> Send for PyAtomicRef<Option<T>> {}
277        unsafe impl<T: Sync + PyPayload> Sync for PyAtomicRef<Option<T>> {}
278        unsafe impl Send for PyAtomicRef<PyObject> {}
279        unsafe impl Sync for PyAtomicRef<PyObject> {}
280        unsafe impl Send for PyAtomicRef<Option<PyObject>> {}
281        unsafe impl Sync for PyAtomicRef<Option<PyObject>> {}
282    }
283    _ => {}
284}
285
286impl<T> fmt::Debug for PyAtomicRef<T> {
287    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
288        write!(f, "PyAtomicRef(")?;
289        // The stored pointer is a `Py<T>` — the full object, header included —
290        // as `Deref`, `load_raw` and `swap` all read it. Formatting it as a
291        // bare payload would skip the header and print misaligned bytes.
292        unsafe {
293            self.inner
294                .load(Ordering::Relaxed)
295                .cast::<PyObject>()
296                .as_ref()
297                .fmt(f)
298        }?;
299        write!(f, ")")
300    }
301}
302
303impl<T: PyPayload> From<PyRef<T>> for PyAtomicRef<T> {
304    fn from(pyref: PyRef<T>) -> Self {
305        let py = PyRef::leak(pyref);
306        let ptr = py as *const _ as *mut u8;
307        // Expose provenance so we can re-derive via with_exposed_provenance
308        // without Stacked Borrows tag restrictions during bootstrap
309        ptr.expose_provenance();
310        Self {
311            inner: Radium::new(ptr),
312            _phantom: Default::default(),
313        }
314    }
315}
316
317impl<T: PyPayload> Deref for PyAtomicRef<T> {
318    type Target = Py<T>;
319
320    fn deref(&self) -> &Self::Target {
321        unsafe {
322            self.inner
323                .load(Ordering::Relaxed)
324                .cast::<Py<T>>()
325                .as_ref()
326                .unwrap_unchecked()
327        }
328    }
329}
330
331impl<T: PyPayload> PyAtomicRef<T> {
332    /// Move a reference into an atomic pointer without creating a Rust
333    /// reference to the pointee. This is only for bootstrap objects whose
334    /// allocation is valid but whose payload is still being initialized.
335    ///
336    /// # Safety
337    /// The pointee must remain allocated, and this atomic reference must not
338    /// be dereferenced until the pointee has been fully initialized.
339    pub(super) unsafe fn from_ref_without_retag(pyref: PyRef<T>) -> Self {
340        let ptr = pyref.into_non_null().as_ptr().cast::<u8>();
341        ptr.expose_provenance();
342        Self {
343            inner: Radium::new(ptr),
344            _phantom: Default::default(),
345        }
346    }
347
348    /// Load the raw pointer without creating a reference.
349    /// Avoids Stacked Borrows retag, safe for use during bootstrap
350    /// when type objects have self-referential pointers being mutated.
351    #[inline(always)]
352    pub(super) fn load_raw(&self) -> *const Py<T> {
353        self.inner.load(Ordering::Relaxed).cast::<Py<T>>()
354    }
355
356    /// # Safety
357    /// The caller is responsible to keep the returned PyRef alive
358    /// until no more reference can be used via PyAtomicRef::deref()
359    #[must_use]
360    pub unsafe fn swap(&self, pyref: PyRef<T>) -> PyRef<T> {
361        let py = PyRef::leak(pyref) as *const Py<T> as *mut _;
362        let old = Radium::swap(&self.inner, py, Ordering::AcqRel);
363        unsafe { PyRef::from_raw(old.cast()) }
364    }
365
366    pub fn swap_to_temporary_refs(&self, pyref: PyRef<T>, vm: &VirtualMachine) {
367        let old = unsafe { self.swap(pyref) };
368        if let Some(frame) = vm.current_frame() {
369            frame.iframe().cold().temporary_refs.lock().push(old.into());
370        }
371    }
372
373    /// Strong reference to the current value.
374    ///
375    /// The cell is never null. A concurrent store may drop the previous value;
376    /// the incref is retried until it applies to the pointer still in the slot.
377    /// A null load is retried rather than forged.
378    pub(crate) fn load_owned(&self) -> PyRef<T> {
379        loop {
380            if let Some(obj) = cell_load_owned(&self.inner) {
381                // SAFETY: this cell is only stored with `PyRef<T>`.
382                return unsafe { obj.downcast_unchecked() };
383            }
384            core::hint::spin_loop();
385        }
386    }
387
388    /// Replace the stored reference. Returns the previous one, still owned.
389    ///
390    /// `None` only if the cell was empty. Callers that publish through
391    /// `From<PyRef<T>>` and `store` keep a `T` in the slot.
392    pub(crate) fn store(&self, value: PyRef<T>) -> Option<PyRef<T>> {
393        cell_store(&self.inner, Some(value.into())).map(|obj| {
394            // SAFETY: this cell is only stored with `PyRef<T>`.
395            unsafe { obj.downcast_unchecked() }
396        })
397    }
398}
399
400impl<T: PyPayload> From<Option<PyRef<T>>> for PyAtomicRef<Option<T>> {
401    fn from(opt_ref: Option<PyRef<T>>) -> Self {
402        let val = opt_ref.map_or(null_mut(), |x| PyRef::leak(x) as *const Py<T> as *mut _);
403        Self {
404            inner: Radium::new(val),
405            _phantom: Default::default(),
406        }
407    }
408}
409
410impl<T: PyPayload> PyAtomicRef<Option<T>> {
411    /// Optional form of PyAtomicRef::from_ref_without_retag.
412    ///
413    /// # Safety
414    /// A non-None pointee must remain allocated, and this atomic reference
415    /// must not be dereferenced until the pointee has been fully initialized.
416    pub(super) unsafe fn from_optional_ref_without_retag(opt_ref: Option<PyRef<T>>) -> Self {
417        let ptr = opt_ref.map_or(null_mut(), |pyref| {
418            pyref.into_non_null().as_ptr().cast::<u8>()
419        });
420        ptr.expose_provenance();
421        Self {
422            inner: Radium::new(ptr),
423            _phantom: Default::default(),
424        }
425    }
426
427    pub fn deref(&self) -> Option<&Py<T>> {
428        self.deref_ordering(Ordering::Relaxed)
429    }
430
431    pub fn deref_ordering(&self, ordering: Ordering) -> Option<&Py<T>> {
432        unsafe { self.inner.load(ordering).cast::<Py<T>>().as_ref() }
433    }
434
435    /// # Safety
436    /// The caller is responsible to keep the returned PyRef alive
437    /// until no more reference can be used via PyAtomicRef::deref()
438    #[must_use]
439    pub unsafe fn swap(&self, opt_ref: Option<PyRef<T>>) -> Option<PyRef<T>> {
440        let val = opt_ref.map_or(null_mut(), |x| PyRef::leak(x) as *const Py<T> as *mut _);
441        let old = Radium::swap(&self.inner, val, Ordering::AcqRel);
442        unsafe { old.cast::<Py<T>>().as_ref().map(|x| PyRef::from_raw(x)) }
443    }
444
445    pub fn swap_to_temporary_refs(&self, opt_ref: Option<PyRef<T>>, vm: &VirtualMachine) {
446        let Some(old) = (unsafe { self.swap(opt_ref) }) else {
447            return;
448        };
449        if let Some(frame) = vm.current_frame() {
450            frame.iframe().cold().temporary_refs.lock().push(old.into());
451        }
452    }
453
454    /// Strong reference to the current value, or `None` when the slot is empty.
455    ///
456    /// This is the owned read for a nullable cell. A concurrent store may drop
457    /// the previous value; the incref is retried until it applies to the
458    /// pointer still in the slot. Published-object memory is reclaimed only
459    /// after a QSBR grace period (see `object::qsbr`), so the refcount word of
460    /// a swapped-out value stays readable.
461    pub fn load_owned(&self) -> Option<PyRef<T>> {
462        cell_load_owned(&self.inner).map(|obj| {
463            // SAFETY: a typed cell only stores references of payload `T`.
464            unsafe { obj.downcast_unchecked() }
465        })
466    }
467
468    /// Replace the stored reference. Returns the previous one, still owned.
469    pub(crate) fn store(&self, value: Option<PyRef<T>>) -> Option<PyRef<T>> {
470        cell_store(&self.inner, value.map(PyObjectRef::from)).map(|obj| {
471            // SAFETY: a typed cell only stores references of payload `T`.
472            unsafe { obj.downcast_unchecked() }
473        })
474    }
475
476    /// Store `value` only when the cell is empty.
477    ///
478    /// On failure the cell is unchanged and `value` is returned still owned.
479    pub(crate) fn compare_exchange_empty(&self, value: PyRef<T>) -> Result<(), PyRef<T>> {
480        // SAFETY: every `PyAtomicRef<_>` is an atomic pointer plus a
481        // zero-sized marker, so the layouts match. The untyped cell API only
482        // loads and stores that pointer.
483        let cell = unsafe { &*core::ptr::from_ref(self).cast::<PyAtomicRef<Option<PyObject>>>() };
484        cell.compare_exchange_empty(value.into()).map_err(|obj| {
485            // SAFETY: a typed cell only stores references of payload `T`.
486            unsafe { obj.downcast_unchecked() }
487        })
488    }
489}
490
491fn cell_load_ptr(inner: &PyAtomic<*mut u8>) -> *mut PyObject {
492    inner.load(Ordering::Acquire).cast()
493}
494
495/// Try-incref the pointer in `inner`.
496///
497/// A concurrent store may drop the previous value. The incref is retried
498/// until it applies to the pointer still in the slot. Returns `None` when
499/// the slot is empty.
500fn cell_load_owned(inner: &PyAtomic<*mut u8>) -> Option<PyObjectRef> {
501    let ptr = inner.load(Ordering::Acquire);
502    if ptr.is_null() {
503        return None;
504    }
505    // Without threading the slot's own reference keeps the object alive,
506    // so one incref is enough. With threading, retry when a store retires
507    // the pointer between the load and the incref.
508    #[cfg(not(feature = "threading"))]
509    {
510        // SAFETY: `ptr` is non-null and the cell's own reference keeps the
511        // object alive for this incref.
512        unsafe { PyObject::try_to_owned_from_ptr(ptr.cast()) }
513    }
514    #[cfg(feature = "threading")]
515    {
516        let mut ptr = ptr;
517        loop {
518            // SAFETY: `ptr` is non-null. A value that left the cell was marked
519            // published, so its refcount word stays readable until QSBR.
520            if let Some(obj) = unsafe { PyObject::try_to_owned_from_ptr(ptr.cast()) }
521                && core::ptr::eq(inner.load(Ordering::Acquire), ptr)
522            {
523                return Some(obj);
524            }
525            ptr = inner.load(Ordering::Acquire);
526            if ptr.is_null() {
527                return None;
528            }
529            core::hint::spin_loop();
530        }
531    }
532}
533
534/// Replace the stored reference. Returns the previous one, still owned.
535///
536/// The value placed in the slot is not marked published, so it can still
537/// return to the freelist. With threading, the value that leaves the slot
538/// is marked so its free waits out a reader that already loaded it.
539fn cell_store(inner: &PyAtomic<*mut u8>, value: Option<PyObjectRef>) -> Option<PyObjectRef> {
540    let new_ptr = match value {
541        Some(obj) => {
542            let ptr = obj.into_raw().as_ptr();
543            ptr.expose_provenance();
544            ptr.cast()
545        }
546        None => null_mut(),
547    };
548    let old = Radium::swap(inner, new_ptr, Ordering::AcqRel);
549    // SAFETY: a non-null slot pointer is an owning reference the cell just released.
550    let old = NonNull::new(old.cast()).map(|ptr| unsafe { PyObjectRef::from_raw(ptr) });
551    #[cfg(feature = "threading")]
552    if let Some(old) = old.as_ref() {
553        old.mark_cache_published();
554    }
555    old
556}
557
558/// Store `value` only when the cell is empty.
559///
560/// On failure the cell is unchanged and `value` is returned still owned.
561fn cell_compare_exchange_empty(
562    inner: &PyAtomic<*mut u8>,
563    value: PyObjectRef,
564) -> Result<(), PyObjectRef> {
565    let raw = value.into_raw();
566    let ptr = raw.as_ptr();
567    ptr.expose_provenance();
568    match inner.compare_exchange(
569        core::ptr::null_mut(),
570        ptr.cast(),
571        Ordering::AcqRel,
572        Ordering::Acquire,
573    ) {
574        Ok(_) => Ok(()),
575        Err(_) => {
576            // SAFETY: the exchange did not take the pointer, so `raw` is
577            // still the unique owning reference.
578            Err(unsafe { PyObjectRef::from_raw(raw) })
579        }
580    }
581}
582
583impl From<PyObjectRef> for PyAtomicRef<PyObject> {
584    fn from(obj: PyObjectRef) -> Self {
585        let obj = obj.into_raw();
586        Self {
587            inner: Radium::new(obj.cast().as_ptr()),
588            _phantom: Default::default(),
589        }
590    }
591}
592
593impl Deref for PyAtomicRef<PyObject> {
594    type Target = PyObject;
595
596    fn deref(&self) -> &Self::Target {
597        unsafe {
598            self.inner
599                .load(Ordering::Relaxed)
600                .cast::<PyObject>()
601                .as_ref()
602                .unwrap_unchecked()
603        }
604    }
605}
606
607impl PyAtomicRef<PyObject> {
608    /// Strong reference to the current value.
609    ///
610    /// The cell is never null. A concurrent store may drop the previous value;
611    /// the incref is retried until it applies to the pointer still in the slot.
612    pub(crate) fn load_owned(&self) -> PyObjectRef {
613        cell_load_owned(&self.inner).expect("non-null atomic cell")
614    }
615
616    /// Replace the stored reference. Returns the previous one, still owned.
617    ///
618    /// The cell is never null, before and after the store.
619    pub(crate) fn store(&self, value: PyObjectRef) -> PyObjectRef {
620        cell_store(&self.inner, Some(value)).expect("non-null atomic cell")
621    }
622
623    /// # Safety
624    /// The caller is responsible to keep the returned PyRef alive
625    /// until no more reference can be used via PyAtomicRef::deref()
626    #[must_use]
627    pub unsafe fn swap(&self, obj: PyObjectRef) -> PyObjectRef {
628        let obj = obj.into_raw();
629        let old = Radium::swap(&self.inner, obj.cast().as_ptr(), Ordering::AcqRel);
630        unsafe { PyObjectRef::from_raw(NonNull::new_unchecked(old.cast())) }
631    }
632
633    pub fn swap_to_temporary_refs(&self, obj: PyObjectRef, vm: &VirtualMachine) {
634        let old = unsafe { self.swap(obj) };
635        if let Some(frame) = vm.current_frame() {
636            frame.iframe().cold().temporary_refs.lock().push(old);
637        }
638    }
639}
640
641impl From<Option<PyObjectRef>> for PyAtomicRef<Option<PyObject>> {
642    fn from(obj: Option<PyObjectRef>) -> Self {
643        let val = obj.map_or(null_mut(), |x| x.into_raw().as_ptr().cast());
644        Self {
645            inner: Radium::new(val),
646            _phantom: Default::default(),
647        }
648    }
649}
650
651impl PyAtomicRef<Option<PyObject>> {
652    /// Empty slot. The pointer is null and owns no reference.
653    pub(crate) const fn new_empty() -> Self {
654        Self {
655            inner: {
656                #[cfg(feature = "threading")]
657                {
658                    core::sync::atomic::AtomicPtr::new(null_mut())
659                }
660                #[cfg(not(feature = "threading"))]
661                {
662                    core::cell::Cell::new(null_mut())
663                }
664            },
665            _phantom: PhantomData,
666        }
667    }
668
669    /// Borrowed pointer currently stored. Null when the slot is empty.
670    ///
671    /// The slot owns the reference, so this stays valid while the slot is
672    /// unchanged. Traversal calls it with other threads stopped.
673    pub(crate) fn load_ptr(&self) -> *mut PyObject {
674        cell_load_ptr(&self.inner)
675    }
676
677    /// Strong reference to the current value, or `None` when the slot is empty.
678    ///
679    /// This is the owned read for a nullable cell. A concurrent store may drop
680    /// the previous value; the incref is retried until it applies to the
681    /// pointer still in the slot. Published-object memory is reclaimed only
682    /// after a QSBR grace period (see `object::qsbr`), so the refcount word of
683    /// a swapped-out value stays readable.
684    pub fn load_owned(&self) -> Option<PyObjectRef> {
685        cell_load_owned(&self.inner)
686    }
687
688    /// Replace the stored reference. Returns the previous one, still owned.
689    pub(crate) fn store(&self, value: Option<PyObjectRef>) -> Option<PyObjectRef> {
690        cell_store(&self.inner, value)
691    }
692
693    /// Store `value` only when the cell is empty.
694    ///
695    /// On failure the cell is unchanged and `value` is returned still owned.
696    pub(crate) fn compare_exchange_empty(&self, value: PyObjectRef) -> Result<(), PyObjectRef> {
697        cell_compare_exchange_empty(&self.inner, value)
698    }
699
700    pub fn deref(&self) -> Option<&PyObject> {
701        self.deref_ordering(Ordering::Relaxed)
702    }
703
704    pub fn deref_ordering(&self, ordering: Ordering) -> Option<&PyObject> {
705        unsafe { self.inner.load(ordering).cast::<PyObject>().as_ref() }
706    }
707
708    /// # Safety
709    /// The caller is responsible to keep the returned PyRef alive
710    /// until no more reference can be used via PyAtomicRef::deref()
711    #[must_use]
712    pub unsafe fn swap(&self, obj: Option<PyObjectRef>) -> Option<PyObjectRef> {
713        let val = obj.map_or(null_mut(), |x| x.into_raw().as_ptr().cast());
714        let old = Radium::swap(&self.inner, val, Ordering::AcqRel);
715        unsafe { NonNull::new(old.cast::<PyObject>()).map(|x| PyObjectRef::from_raw(x)) }
716    }
717
718    pub fn swap_to_temporary_refs(&self, obj: Option<PyObjectRef>, vm: &VirtualMachine) {
719        let Some(old) = (unsafe { self.swap(obj) }) else {
720            return;
721        };
722        if let Some(frame) = vm.current_frame() {
723            frame.iframe().cold().temporary_refs.lock().push(old);
724        }
725    }
726}
727
728/// A nullable object reference that can be replaced without invalidating readers.
729///
730/// Unlike [`PyAtomicRef`], this cell only exposes owned reads. Concurrent stores
731/// use the same QSBR reclamation as object slots without retaining old values
732/// for the lifetime of a Python frame.
733#[repr(transparent)]
734pub struct PyObjectCell(PyAtomicRef<Option<PyObject>>);
735
736impl From<Option<PyObjectRef>> for PyObjectCell {
737    fn from(value: Option<PyObjectRef>) -> Self {
738        Self(value.into())
739    }
740}
741
742impl PyObjectCell {
743    /// Return an owned reference to the current value, or `None` for an empty cell.
744    #[inline]
745    pub fn load_owned(&self) -> Option<PyObjectRef> {
746        self.0.load_owned()
747    }
748
749    /// Replace the stored value and return the previous reference, still owned.
750    #[inline]
751    pub fn store(&self, value: Option<PyObjectRef>) -> Option<PyObjectRef> {
752        self.0.store(value)
753    }
754}
755
756impl fmt::Debug for PyObjectCell {
757    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
758        f.debug_tuple("PyObjectCell")
759            .field(&self.load_owned())
760            .finish()
761    }
762}
763
764// SAFETY: the underlying cell visits its single owned reference while mutating
765// threads are stopped. It does not clone the reference during traversal.
766unsafe impl Traverse for PyObjectCell {
767    #[inline]
768    fn traverse(&self, traverse_fn: &mut TraverseFn<'_>) {
769        self.0.traverse(traverse_fn);
770    }
771}
772
773// Object members address a single pointer-sized cell at the field's offset.
774const _: () = assert!(
775    core::mem::size_of::<PyObjectCell>() == core::mem::size_of::<*mut PyObject>()
776        && core::mem::align_of::<PyObjectCell>() == core::mem::align_of::<*mut PyObject>()
777);
778
779/// Atomic borrowed (non-ref-counted) optional reference to a Python object.
780/// Unlike `PyAtomicRef`, this does NOT own the reference.
781/// The pointed-to object must outlive this reference.
782pub struct PyAtomicBorrow {
783    inner: PyAtomic<*mut u8>,
784}
785
786// Safety: Access patterns ensure the pointed-to object outlives this reference.
787// The owner (generator/coroutine) clears this in its Drop impl before deallocation.
788unsafe impl Send for PyAtomicBorrow {}
789unsafe impl Sync for PyAtomicBorrow {}
790
791impl PyAtomicBorrow {
792    #[must_use]
793    pub fn new() -> Self {
794        Self {
795            inner: Radium::new(null_mut()),
796        }
797    }
798
799    pub fn store(&self, obj: &PyObject) {
800        let ptr = obj as *const PyObject as *mut u8;
801        Radium::store(&self.inner, ptr, Ordering::Relaxed);
802    }
803
804    pub fn load(&self) -> Option<&PyObject> {
805        let ptr = Radium::load(&self.inner, Ordering::Relaxed);
806        if ptr.is_null() {
807            None
808        } else {
809            Some(unsafe { &*(ptr as *const PyObject) })
810        }
811    }
812
813    pub fn clear(&self) {
814        Radium::store(&self.inner, null_mut(), Ordering::Relaxed);
815    }
816
817    pub fn to_owned(&self) -> Option<PyObjectRef> {
818        self.load().map(|obj| obj.to_owned())
819    }
820}
821
822impl Default for PyAtomicBorrow {
823    fn default() -> Self {
824        Self::new()
825    }
826}
827
828impl fmt::Debug for PyAtomicBorrow {
829    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
830        write!(
831            f,
832            "PyAtomicBorrow({:?})",
833            Radium::load(&self.inner, Ordering::Relaxed)
834        )
835    }
836}
837
838pub trait AsObject
839where
840    Self: Borrow<PyObject>,
841{
842    #[inline(always)]
843    fn as_object(&self) -> &PyObject {
844        self.borrow()
845    }
846
847    #[inline(always)]
848    fn get_id(&self) -> usize {
849        self.as_object().unique_id()
850    }
851
852    #[inline(always)]
853    fn is<T>(&self, other: &T) -> bool
854    where
855        T: AsObject,
856    {
857        self.get_id() == other.get_id()
858    }
859
860    #[inline(always)]
861    fn class(&self) -> &Py<PyType> {
862        self.as_object().class()
863    }
864
865    fn get_class_attr(&self, attr_name: &'static PyStrInterned) -> Option<PyObjectRef> {
866        self.class().get_attr(attr_name)
867    }
868
869    /// Determines if `obj` actually an instance of `cls`, this doesn't call __instancecheck__, so only
870    /// use this if `cls` is known to have not overridden the base __instancecheck__ magic method.
871    #[inline]
872    fn fast_isinstance(&self, cls: &Py<PyType>) -> bool {
873        self.class().fast_issubclass(cls)
874    }
875}
876
877impl<T> AsObject for T where T: Borrow<PyObject> {}
878
879impl PyObject {
880    #[inline(always)]
881    fn unique_id(&self) -> usize {
882        self as *const Self as usize
883    }
884}
885
886// impl<T: ?Sized> Borrow<PyObject> for PyRc<T> {
887//     #[inline(always)]
888//     fn borrow(&self) -> &PyObject {
889//         unsafe { &*(&**self as *const T as *const PyObject) }
890//     }
891// }
892
893impl<T: PyPayload> ToPyObject for PyRef<T> {
894    #[inline(always)]
895    fn to_pyobject(self, _vm: &VirtualMachine) -> PyObjectRef {
896        self.into()
897    }
898}
899
900impl ToPyObject for PyObjectRef {
901    #[inline(always)]
902    fn to_pyobject(self, _vm: &VirtualMachine) -> PyObjectRef {
903        self
904    }
905}
906
907impl ToPyObject for &PyObject {
908    #[inline(always)]
909    fn to_pyobject(self, _vm: &VirtualMachine) -> PyObjectRef {
910        self.to_owned()
911    }
912}
913
914// Allows a built-in function to return any built-in object payload without
915// explicitly implementing `ToPyObject`.
916impl<T> ToPyObject for T
917where
918    T: PyPayload + core::fmt::Debug + Sized,
919{
920    #[inline(always)]
921    fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
922        PyPayload::into_pyobject(self, vm)
923    }
924}
925
926impl<T> ToPyResult for T
927where
928    T: ToPyObject,
929{
930    #[inline(always)]
931    fn to_pyresult(self, vm: &VirtualMachine) -> PyResult {
932        Ok(self.to_pyobject(vm))
933    }
934}
935
936impl<T, E> ToPyResult for Result<T, E>
937where
938    T: ToPyObject,
939    E: IntoPyException,
940{
941    #[inline(always)]
942    fn to_pyresult(self, vm: &VirtualMachine) -> PyResult {
943        self.map(|res| T::to_pyobject(res, vm))
944            .map_err(|e| E::into_pyexception(e, vm))
945    }
946}
947
948impl IntoPyException for PyBaseExceptionRef {
949    #[inline(always)]
950    fn into_pyexception(self, _vm: &VirtualMachine) -> PyBaseExceptionRef {
951        self
952    }
953}
954
955#[cfg(test)]
956mod tests {
957    use super::*;
958
959    #[test]
960    fn object_cell_snapshots_survive_replacement_and_clear() {
961        crate::Interpreter::without_stdlib(Default::default()).enter(|vm| {
962            let cell = PyObjectCell::from(Some(vm.ctx.new_bytes(vec![1, 2, 3]).into()));
963            let first = cell.load_owned().unwrap();
964            let previous = cell.store(Some(vm.ctx.new_bytes(vec![4, 5, 6]).into()));
965            assert!(previous.as_ref().unwrap().is(&first));
966            drop(previous);
967            assert_eq!(first.strong_count(), 1);
968            assert_eq!(
969                first
970                    .downcast_ref::<crate::builtins::PyBytes>()
971                    .unwrap()
972                    .as_bytes(),
973                &[1, 2, 3]
974            );
975
976            let second = cell.load_owned().unwrap();
977            let previous = cell.store(None);
978            assert!(previous.as_ref().unwrap().is(&second));
979            drop(previous);
980            assert!(cell.load_owned().is_none());
981            assert!(cell.store(None).is_none());
982            assert_eq!(second.strong_count(), 1);
983            assert_eq!(
984                second
985                    .downcast_ref::<crate::builtins::PyBytes>()
986                    .unwrap()
987                    .as_bytes(),
988                &[4, 5, 6]
989            );
990        });
991    }
992
993    #[test]
994    fn object_cell_traverses_current_reference_once_without_cloning() {
995        crate::Interpreter::without_stdlib(Default::default()).enter(|vm| {
996            let cell = PyObjectCell::from(None);
997            cell.traverse(&mut |_| panic!("empty cell owns no edge"));
998            let value: PyObjectRef = vm.ctx.new_bytes(vec![1, 2, 3]).into();
999            assert!(cell.store(Some(value.clone())).is_none());
1000            let references = value.strong_count();
1001            let mut edges = 0;
1002            cell.traverse(&mut |child| {
1003                assert!(child.is(&value));
1004                assert_eq!(value.strong_count(), references);
1005                edges += 1;
1006            });
1007            assert_eq!(edges, 1);
1008            drop(cell.store(None));
1009            cell.traverse(&mut |_| panic!("cleared cell owns no edge"));
1010        });
1011    }
1012}