1use super::{
14 PyAtomicRef,
15 ext::{AsObject, PyRefExact, PyResult},
16 payload::PyPayload,
17};
18use crate::object::traverse_object::PyObjVTable;
19use crate::{
20 builtins::{PyDictRef, PyTuple, PyTupleRef, PyType, PyTypeRef, type_::PyTypeTupleRef},
21 common::{
22 atomic::{Ordering, PyAtomic, Radium},
23 linked_list::{Link, Pointers},
24 lock::PyRwLock,
25 refcount::RefCount,
26 },
27 vm::VirtualMachine,
28};
29use crate::{
30 class::StaticType,
31 object::traverse::{MaybeTraverse, Traverse, TraverseFn},
32};
33
34use alloc::fmt;
35
36use core::{
37 any::TypeId,
38 borrow::Borrow,
39 cell::UnsafeCell,
40 marker::PhantomData,
41 mem::ManuallyDrop,
42 num::NonZeroUsize,
43 ops::Deref,
44 ptr::{self, NonNull},
45};
46
47#[derive(Debug)]
82pub(super) struct Erased;
83
84mod trashcan {
88 use core::cell::Cell;
89
90 const TRASHCAN_LIMIT: usize = 50;
93
94 type DeallocFn = unsafe fn(*mut super::PyObject);
95 type DeallocQueue = Vec<(*mut super::PyObject, DeallocFn)>;
96
97 struct Trashcan {
103 depth: Cell<usize>,
104 queue: Cell<DeallocQueue>,
105 }
106
107 thread_local! {
108 static TRASHCAN: Trashcan = const {
109 Trashcan {
110 depth: Cell::new(0),
111 queue: Cell::new(Vec::new()),
112 }
113 };
114 }
115
116 #[inline]
119 pub(super) unsafe fn begin(
120 obj: *mut super::PyObject,
121 dealloc: unsafe fn(*mut super::PyObject),
122 ) -> bool {
123 TRASHCAN.with(|t| {
124 let depth = t.depth.get();
125 if depth >= TRASHCAN_LIMIT {
126 let mut queue = t.queue.take();
128 queue.push((obj, dealloc));
129 t.queue.set(queue);
130 false
131 } else {
132 t.depth.set(depth + 1);
133 true
134 }
135 })
136 }
137
138 #[inline]
140 pub(super) unsafe fn end() {
141 TRASHCAN.with(|t| {
142 let depth = t.depth.get();
143 debug_assert!(depth > 0, "trashcan::end called without matching begin");
144 let depth = depth - 1;
145 t.depth.set(depth);
146 if depth != 0 {
147 return;
148 }
149 loop {
152 let next = {
153 let mut queue = t.queue.take();
154 let item = queue.pop();
155 t.queue.set(queue);
156 item
157 };
158 if let Some((obj, dealloc)) = next {
159 unsafe { dealloc(obj) };
160 } else {
161 break;
162 }
163 }
164 })
165 }
166}
167
168pub(super) unsafe fn default_dealloc<T: PyPayload>(obj: *mut PyObject) {
171 let obj_ref = unsafe { &*(obj as *const PyObject) };
172 if let Err(()) = obj_ref.drop_slow_inner() {
173 return; }
175
176 let tracked = obj_ref.is_gc_tracked();
184
185 if tracked && !unsafe { trashcan::begin(obj, default_dealloc::<T>) } {
187 return; }
189
190 let vtable = obj_ref.0.vtable;
191
192 if tracked {
196 let ptr = unsafe { NonNull::new_unchecked(obj) };
197 unsafe {
198 crate::gc_state::gc_state().untrack_object(ptr);
199 }
200 debug_assert!(
202 !obj_ref.is_gc_tracked(),
203 "object still tracked after untrack_object"
204 );
205 debug_assert_eq!(
206 obj_ref.gc_generation(),
207 crate::object::GC_UNTRACKED,
208 "gc_generation not reset after untrack_object"
209 );
210 }
211
212 let mut edges = Vec::new();
216 if let Some(clear_fn) = vtable.clear {
217 unsafe { clear_fn(obj, &mut edges) };
218 }
219 drop(edges);
221
222 let typ = obj_ref.class();
236 let pushed = if T::HAS_FREELIST
237 && typ.heaptype_ext.is_none()
238 && core::ptr::eq(typ, T::class(crate::vm::Context::genesis()))
239 && !obj_ref.0.ref_count.is_published()
240 {
241 if let Some(ext) = obj_ref.0.ext_ref() {
242 if obj_ref
243 .class()
244 .slots
245 .flags
246 .has_feature(crate::types::PyTypeFlags::HAS_DICT)
247 {
248 drop(ext.dict.replace(None));
249 }
250 for slot in obj_ref.0.slot_cells() {
251 drop(slot.store(None));
252 }
253 }
254 unsafe { T::freelist_push(obj) }
255 } else {
256 false
257 };
258
259 if !pushed {
260 unsafe { Py::dealloc(obj as *mut Py<T>) };
262 }
263
264 if tracked {
266 unsafe { trashcan::end() };
267 }
268}
269pub(super) unsafe fn debug_obj<T: PyPayload + core::fmt::Debug>(
270 x: &PyObject,
271 f: &mut fmt::Formatter<'_>,
272) -> fmt::Result {
273 let x = unsafe { &*(x as *const PyObject as *const Py<T>) };
274 write!(f, "[PyObject {:?}]", x.payload)
275}
276
277pub(super) unsafe fn try_traverse_obj<T: PyPayload>(x: &PyObject, tracer_fn: &mut TraverseFn<'_>) {
279 let x = unsafe { &*(x as *const PyObject as *const Py<T>) };
280 let payload = &x.payload;
281 payload.try_traverse(tracer_fn)
282}
283
284pub(super) unsafe fn try_clear_obj<T: PyPayload>(x: *mut PyObject, out: &mut Vec<PyObjectRef>) {
286 let x = unsafe { &mut *(x as *mut Py<T>) };
287 x.payload.try_clear(out);
288}
289
290bitflags::bitflags! {
291 #[derive(Copy, Clone, Debug, Default)]
295 pub(crate) struct GcBits: u8 {
296 const TRACKED = 1 << 0;
298 const FINALIZED = 1 << 1;
300 const UNREACHABLE = 1 << 2;
302 const FROZEN = 1 << 3;
304 const SHARED = 1 << 4;
307 const SHARED_INLINE = 1 << 5;
310 const DEFERRED = 1 << 6;
312 const COLLECTING = 1 << 7;
315 }
316}
317
318pub(crate) const GC_UNTRACKED: u8 = 0xFF;
320pub(crate) const GC_PERMANENT: u8 = 3;
321pub(crate) type GcOwner = u16;
328
329pub(crate) const GC_NO_OWNER: GcOwner = 0;
333
334pub(crate) const GC_REACHABLE: u32 = u32::MAX;
338
339pub(crate) struct GcLink;
341
342unsafe impl Link for GcLink {
345 type Handle = NonNull<PyObject>;
346 type Target = PyObject;
347
348 fn as_raw(handle: &NonNull<PyObject>) -> NonNull<PyObject> {
349 *handle
350 }
351
352 unsafe fn from_raw(ptr: NonNull<PyObject>) -> NonNull<PyObject> {
353 ptr
354 }
355
356 unsafe fn pointers(target: NonNull<PyObject>) -> NonNull<Pointers<PyObject>> {
357 let inner_ptr = target.as_ptr() as *mut Py<Erased>;
358 unsafe { NonNull::new_unchecked(&raw mut (*inner_ptr).gc_pointers) }
359 }
360}
361
362#[repr(C, align(8))]
371pub(super) struct ObjExt {
372 pub(super) dict: InstanceDict,
376}
377
378impl ObjExt {
379 fn new(dict: Option<PyDictRef>, has_dict: bool, inline_values: bool) -> Self {
380 Self {
381 dict: if has_dict {
382 InstanceDict::from_opt(dict, inline_values)
383 } else {
384 InstanceDict::from_opt(None, false)
385 },
386 }
387 }
388}
389
390impl fmt::Debug for ObjExt {
391 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
392 write!(f, "[ObjExt]")
393 }
394}
395
396const EXT_OFFSET: usize = core::mem::size_of::<ObjExt>();
401
402pub(crate) fn slot_member_offset(index: usize) -> isize {
408 let cell = core::mem::size_of::<PyAtomicRef<Option<PyObject>>>();
411 -((EXT_OFFSET + (index + 1) * cell) as isize)
412}
413
414fn slot_region_layout(member_count: usize) -> Option<core::alloc::Layout> {
415 if member_count == 0 {
416 return None;
417 }
418 let cell = core::mem::size_of::<PyAtomicRef<Option<PyObject>>>();
419 let bytes = member_count * cell;
420 let align = core::mem::align_of::<ObjExt>();
421 let pad = (align - bytes % align) % align;
425 Some(core::alloc::Layout::from_size_align(pad + bytes, align).unwrap())
426}
427const WEAKREF_OFFSET: usize = core::mem::size_of::<WeakRefList>();
428
429fn weakref_prefix_offset(has_ext: bool, member_count: usize) -> usize {
432 let ext = if has_ext { EXT_OFFSET } else { 0 };
433 let slots = slot_region_layout(member_count).map_or(0, |layout| layout.size());
434 ext + slots + WEAKREF_OFFSET
435}
436
437const _: () =
438 assert!(core::mem::size_of::<ObjExt>().is_multiple_of(core::mem::align_of::<ObjExt>()));
439const _: () = assert!(core::mem::align_of::<ObjExt>() >= core::mem::align_of::<Py<()>>());
440const _: () = assert!(
441 core::mem::size_of::<WeakRefList>().is_multiple_of(core::mem::align_of::<WeakRefList>())
442);
443const _: () = assert!(core::mem::align_of::<WeakRefList>() >= core::mem::align_of::<Py<()>>());
444
445#[repr(C)]
449pub struct Py<T> {
450 pub(super) ref_count: RefCount,
451 pub(super) vtable: &'static PyObjVTable,
452 pub(super) gc_bits: PyAtomic<u8>,
454 pub(super) gc_generation: PyAtomic<u8>,
457 pub(super) gc_owner: PyAtomic<GcOwner>,
461 pub(super) gc_refs: PyAtomic<u32>,
466 pub(super) gc_pointers: Pointers<PyObject>,
468
469 pub(super) typ: PyAtomicRef<PyType>, pub(crate) payload: T,
472}
473pub const SIZEOF_PYOBJECT_HEAD: usize = core::mem::size_of::<Py<()>>();
474
475#[must_use]
477#[inline]
478pub const fn payload_offset<T>() -> usize {
479 core::mem::offset_of!(Py<T>, payload)
480}
481
482#[must_use]
487#[inline]
488pub const fn dict_member_offset() -> isize {
489 -(core::mem::size_of::<ObjExt>() as isize)
490}
491
492const _: () = assert!(SIZEOF_PYOBJECT_HEAD == 5 * core::mem::size_of::<usize>() + 8);
498
499const _: () = assert!(
502 !core::mem::needs_drop::<RefCount>()
503 && !core::mem::needs_drop::<&'static PyObjVTable>()
504 && !core::mem::needs_drop::<PyAtomic<u8>>()
505 && !core::mem::needs_drop::<PyAtomic<u32>>()
506 && !core::mem::needs_drop::<PyAtomic<GcOwner>>()
507 && !core::mem::needs_drop::<Pointers<PyObject>>()
508);
509
510impl<T> Py<T> {
511 #[inline(always)]
514 fn read_type_flags(&self) -> (crate::types::PyTypeFlags, usize) {
515 let typ_ptr = self.typ.load_raw();
516 let slots = unsafe { core::ptr::addr_of!((*typ_ptr).payload.slots) };
517 let bits = unsafe {
521 (*core::ptr::addr_of!((*slots).flags))
522 .as_bits()
523 .load(core::sync::atomic::Ordering::Acquire)
524 };
525 let member_count = unsafe { core::ptr::addr_of!((*slots).member_count).read() };
526 (
527 crate::types::PyTypeFlags::from_bits_truncate(bits),
528 member_count,
529 )
530 }
531
532 #[inline(always)]
538 pub(super) fn ext_ref(&self) -> Option<&ObjExt> {
539 let (flags, member_count) = self.read_type_flags();
540 let has_ext = flags.contains(&crate::types::PyTypeFlags::HAS_DICT) || member_count > 0;
541 if !has_ext {
542 return None;
543 }
544 let self_addr = (self as *const Self as *const u8).addr();
545 let ext_ptr =
546 core::ptr::with_exposed_provenance::<ObjExt>(self_addr.wrapping_sub(EXT_OFFSET));
547 Some(unsafe { &*ext_ptr })
548 }
549
550 pub(super) fn slot_cells(&self) -> &[PyAtomicRef<Option<PyObject>>] {
553 let Some(ext) = self.ext_ref() else {
554 return &[];
555 };
556 let (_, member_count) = self.read_type_flags();
557 if member_count == 0 {
558 return &[];
559 }
560 let cell = core::mem::size_of::<PyAtomicRef<Option<PyObject>>>();
561 let first = (ext as *const ObjExt)
563 .addr()
564 .wrapping_sub(member_count * cell);
565 let ptr = core::ptr::with_exposed_provenance::<PyAtomicRef<Option<PyObject>>>(first);
566 unsafe { core::slice::from_raw_parts(ptr, member_count) }
567 }
568
569 #[inline(always)]
576 pub(super) fn weakref_list_ref(&self) -> Option<&WeakRefList> {
577 let (flags, member_count) = self.read_type_flags();
578 if !flags.contains(&crate::types::PyTypeFlags::HAS_WEAKREF) {
579 return None;
580 }
581 let has_ext = flags.contains(&crate::types::PyTypeFlags::HAS_DICT) || member_count > 0;
582 let self_addr = (self as *const Self as *const u8).addr();
583 let ptr = core::ptr::with_exposed_provenance::<WeakRefList>(
584 self_addr.wrapping_sub(weakref_prefix_offset(has_ext, member_count)),
585 );
586 Some(unsafe { &*ptr })
587 }
588}
589
590unsafe impl Traverse for PyObject {
591 fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
594 self.0.traverse(tracer_fn)
595 }
596}
597
598#[cfg(feature = "threading")]
601mod weakref_lock {
602 use core::sync::atomic::{AtomicU8, Ordering};
603
604 const NUM_WEAKREF_LOCKS: usize = 64;
605
606 static LOCKS: [AtomicU8; NUM_WEAKREF_LOCKS] = [const { AtomicU8::new(0) }; NUM_WEAKREF_LOCKS];
607
608 pub(super) struct WeakrefLockGuard {
609 idx: usize,
610 }
611
612 impl Drop for WeakrefLockGuard {
613 fn drop(&mut self) {
614 LOCKS[self.idx].store(0, Ordering::Release);
615 }
616 }
617
618 pub(super) fn lock(addr: usize) -> WeakrefLockGuard {
619 let idx = (addr >> 4) % NUM_WEAKREF_LOCKS;
620 loop {
621 if LOCKS[idx]
622 .compare_exchange_weak(0, 1, Ordering::Acquire, Ordering::Relaxed)
623 .is_ok()
624 {
625 return WeakrefLockGuard { idx };
626 }
627 core::hint::spin_loop();
628 }
629 }
630
631 #[cfg(all(unix, feature = "host_env"))]
634 pub(crate) fn reset_all_after_fork() {
635 for lock in &LOCKS {
636 lock.store(0, Ordering::Release);
637 }
638 }
639}
640
641#[cfg(not(feature = "threading"))]
642mod weakref_lock {
643 pub(super) struct WeakrefLockGuard;
644
645 impl Drop for WeakrefLockGuard {
646 fn drop(&mut self) {}
647 }
648
649 pub(super) fn lock(_addr: usize) -> WeakrefLockGuard {
650 WeakrefLockGuard
651 }
652}
653
654#[cfg(all(unix, feature = "threading", feature = "host_env"))]
657pub(crate) fn reset_weakref_locks_after_fork() {
658 weakref_lock::reset_all_after_fork();
659}
660
661#[repr(C)]
664pub(super) struct WeakRefList {
665 head: PyAtomic<*mut Py<PyWeak>>,
667 generic: PyAtomic<*mut Py<PyWeak>>,
670}
671
672impl fmt::Debug for WeakRefList {
673 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
674 f.debug_struct("WeakRefList").finish_non_exhaustive()
675 }
676}
677
678unsafe fn unlink_weakref(wrl: &WeakRefList, node: NonNull<Py<PyWeak>>) {
683 unsafe {
684 let mut ptrs = WeakLink::pointers(node);
685 let prev = ptrs.as_ref().get_prev();
686 let next = ptrs.as_ref().get_next();
687
688 if let Some(prev) = prev {
689 WeakLink::pointers(prev).as_mut().set_next(next);
690 } else {
691 wrl.head.store(
693 next.map_or(ptr::null_mut(), |p| p.as_ptr()),
694 Ordering::Relaxed,
695 );
696 }
697 if let Some(next) = next {
698 WeakLink::pointers(next).as_mut().set_prev(prev);
699 }
700
701 ptrs.as_mut().set_prev(None);
702 ptrs.as_mut().set_next(None);
703 }
704}
705
706unsafe fn try_reuse_weakref(ptr: *mut Py<PyWeak>) -> Option<PyRef<PyWeak>> {
708 if ptr.is_null() {
709 return None;
710 }
711 let node = unsafe { &*ptr };
712 node.ref_count
713 .safe_inc()
714 .then(|| unsafe { PyRef::from_raw(ptr) })
715}
716
717impl WeakRefList {
718 pub(super) fn new() -> Self {
719 Self {
720 head: Radium::new(ptr::null_mut()),
721 generic: Radium::new(ptr::null_mut()),
722 }
723 }
724
725 fn add(
727 &self,
728 obj: &PyObject,
729 cls: PyTypeRef,
730 cls_is_weakref: bool,
731 cls_is_weakproxy: bool,
732 callback: Option<PyObjectRef>,
733 dict: Option<PyDictRef>,
734 ) -> PyRef<PyWeak> {
735 let is_generic = cls_is_weakref && callback.is_none();
736 let is_generic_proxy = cls_is_weakproxy && callback.is_none();
737
738 {
740 let _lock = weakref_lock::lock(obj as *const PyObject as usize);
741 let existing = if is_generic {
742 unsafe { try_reuse_weakref(self.generic.load(Ordering::Relaxed)) }
743 } else if is_generic_proxy {
744 unsafe { try_reuse_weakref(self.find_generic_proxy_ptr()) }
745 } else {
746 None
747 };
748 if let Some(existing) = existing {
749 return existing;
750 }
751 }
752
753 let weak_payload = PyWeak {
757 pointers: Pointers::new(),
758 wr_object: Radium::new(obj as *const PyObject as *mut PyObject),
759 callback: UnsafeCell::new(callback),
760 hash: Radium::new(crate::common::hash::SENTINEL),
761 };
762 let weak = PyRef::new_ref(weak_payload, cls, dict);
763
764 let _lock = weakref_lock::lock(obj as *const PyObject as usize);
766
767 let existing = if is_generic {
771 unsafe { try_reuse_weakref(self.generic.load(Ordering::Relaxed)) }
772 } else if is_generic_proxy {
773 unsafe { try_reuse_weakref(self.find_generic_proxy_ptr()) }
774 } else {
775 None
776 };
777 if let Some(existing) = existing {
778 weak.wr_object.store(ptr::null_mut(), Ordering::Relaxed);
781 return existing;
782 }
783
784 let node_ptr = NonNull::from(&*weak);
787 let after = if is_generic {
788 None
789 } else if is_generic_proxy {
790 NonNull::new(self.generic.load(Ordering::Relaxed))
791 } else {
792 NonNull::new(self.find_generic_proxy_ptr())
793 .or_else(|| NonNull::new(self.generic.load(Ordering::Relaxed)))
794 };
795 match after {
796 Some(after) => unsafe { self.insert_after(after, node_ptr) },
797 None => unsafe { self.insert_at_head(node_ptr) },
798 }
799 if is_generic {
800 self.generic.store(node_ptr.as_ptr(), Ordering::Relaxed);
801 }
802
803 weak
804 }
805
806 unsafe fn insert_at_head(&self, node_ptr: NonNull<Py<PyWeak>>) {
807 unsafe {
808 let mut ptrs = WeakLink::pointers(node_ptr);
809 let old_head = self.head.load(Ordering::Relaxed);
810 ptrs.as_mut().set_next(NonNull::new(old_head));
811 ptrs.as_mut().set_prev(None);
812 if let Some(old_head) = NonNull::new(old_head) {
813 WeakLink::pointers(old_head)
814 .as_mut()
815 .set_prev(Some(node_ptr));
816 }
817 self.head.store(node_ptr.as_ptr(), Ordering::Relaxed);
818 }
819 }
820
821 unsafe fn insert_after(&self, after: NonNull<Py<PyWeak>>, node_ptr: NonNull<Py<PyWeak>>) {
822 unsafe {
823 let mut ptrs = WeakLink::pointers(node_ptr);
824 let after_next = WeakLink::pointers(after).as_ref().get_next();
825 ptrs.as_mut().set_prev(Some(after));
826 ptrs.as_mut().set_next(after_next);
827 WeakLink::pointers(after).as_mut().set_next(Some(node_ptr));
828 if let Some(next) = after_next {
829 WeakLink::pointers(next).as_mut().set_prev(Some(node_ptr));
830 }
831 }
832 }
833
834 fn find_generic_proxy_ptr(&self) -> *mut Py<PyWeak> {
836 let generic_ptr = self.generic.load(Ordering::Relaxed);
837 let candidate_ptr = if let Some(generic_node) = NonNull::new(generic_ptr) {
838 unsafe { WeakLink::pointers(generic_node).as_ref().get_next() }
839 .map_or(ptr::null_mut(), |n| n.as_ptr())
840 } else {
841 self.head.load(Ordering::Relaxed)
842 };
843 match NonNull::new(candidate_ptr) {
844 Some(candidate) => {
845 let node = unsafe { candidate.as_ref() };
846 let has_callback = unsafe { (&*node.payload.callback.get()).is_some() };
847 let node_cls = node.class();
848 let is_proxy = node_cls.is(crate::builtins::PyWeakProxy::static_type())
852 || node_cls.is(crate::builtins::PyWeakCallableProxy::static_type());
853 if has_callback || !is_proxy {
854 ptr::null_mut()
855 } else {
856 candidate_ptr
857 }
858 }
859 None => ptr::null_mut(),
860 }
861 }
862
863 fn clear(&self, obj: &PyObject) {
867 let obj_addr = obj as *const PyObject as usize;
868 let _lock = weakref_lock::lock(obj_addr);
869
870 self.generic.store(ptr::null_mut(), Ordering::Relaxed);
872
873 let mut callbacks: Vec<(PyRef<PyWeak>, PyObjectRef)> = Vec::new();
875 let mut current = NonNull::new(self.head.load(Ordering::Relaxed));
876 while let Some(node) = current {
877 let next = unsafe { WeakLink::pointers(node).as_ref().get_next() };
878
879 let wr = unsafe { node.as_ref() };
880
881 wr.payload
883 .wr_object
884 .store(ptr::null_mut(), Ordering::Relaxed);
885
886 unsafe {
888 let mut ptrs = WeakLink::pointers(node);
889 ptrs.as_mut().set_prev(None);
890 ptrs.as_mut().set_next(None);
891 }
892
893 if wr.ref_count.safe_inc() {
895 let wr_ref = unsafe { PyRef::from_raw(wr as *const Py<PyWeak>) };
896 let cb = unsafe { wr.payload.callback.get().replace(None) };
897 if let Some(cb) = cb {
898 callbacks.push((wr_ref, cb));
899 }
900 }
901
902 current = next;
903 }
904 self.head.store(ptr::null_mut(), Ordering::Relaxed);
905
906 drop(_lock);
908 for (wr, cb) in callbacks {
909 crate::vm::thread::with_vm(&cb, |vm| {
910 let _ = cb.call((wr.clone(),), vm);
911 });
912 }
913 }
914
915 fn clear_for_gc_collect_callbacks(&self, obj: &PyObject) -> Vec<(PyRef<PyWeak>, PyObjectRef)> {
919 let obj_addr = obj as *const PyObject as usize;
920 let _lock = weakref_lock::lock(obj_addr);
921
922 self.generic.store(ptr::null_mut(), Ordering::Relaxed);
924
925 let mut callbacks = Vec::new();
926 let mut current = NonNull::new(self.head.load(Ordering::Relaxed));
927 while let Some(node) = current {
928 let next = unsafe { WeakLink::pointers(node).as_ref().get_next() };
929
930 let wr = unsafe { node.as_ref() };
931
932 wr.payload
934 .wr_object
935 .store(ptr::null_mut(), Ordering::Relaxed);
936
937 unsafe {
939 let mut ptrs = WeakLink::pointers(node);
940 ptrs.as_mut().set_prev(None);
941 ptrs.as_mut().set_next(None);
942 }
943
944 if wr.ref_count.safe_inc() {
946 let wr_ref = unsafe { PyRef::from_raw(wr as *const Py<PyWeak>) };
947 let cb = unsafe { wr.payload.callback.get().replace(None) };
948 if let Some(cb) = cb {
949 callbacks.push((wr_ref, cb));
950 }
951 }
952
953 current = next;
954 }
955 self.head.store(ptr::null_mut(), Ordering::Relaxed);
956
957 callbacks
958 }
959
960 fn count(&self, obj: &PyObject) -> usize {
961 let _lock = weakref_lock::lock(obj as *const PyObject as usize);
962 let mut count = 0usize;
963 let mut current = NonNull::new(self.head.load(Ordering::Relaxed));
964 while let Some(node) = current {
965 if unsafe { node.as_ref() }.ref_count.get() > 0 {
966 count += 1;
967 }
968 current = unsafe { WeakLink::pointers(node).as_ref().get_next() };
969 }
970 count
971 }
972
973 fn get_weak_references(&self, obj: &PyObject) -> Vec<PyRef<PyWeak>> {
974 let _lock = weakref_lock::lock(obj as *const PyObject as usize);
975 let mut v = Vec::new();
976 let mut current = NonNull::new(self.head.load(Ordering::Relaxed));
977 while let Some(node) = current {
978 let wr = unsafe { node.as_ref() };
979 if wr.ref_count.safe_inc() {
980 v.push(unsafe { PyRef::from_raw(wr as *const Py<PyWeak>) });
981 }
982 current = unsafe { WeakLink::pointers(node).as_ref().get_next() };
983 }
984 v
985 }
986}
987
988impl Default for WeakRefList {
989 fn default() -> Self {
990 Self::new()
991 }
992}
993
994struct WeakLink;
995unsafe impl Link for WeakLink {
996 type Handle = PyRef<PyWeak>;
997
998 type Target = Py<PyWeak>;
999
1000 #[inline(always)]
1001 fn as_raw(handle: &PyRef<PyWeak>) -> NonNull<Self::Target> {
1002 NonNull::from(&**handle)
1003 }
1004
1005 #[inline(always)]
1006 unsafe fn from_raw(ptr: NonNull<Self::Target>) -> Self::Handle {
1007 unsafe { PyRef::from_raw(ptr.as_ptr()) }
1008 }
1009
1010 #[inline(always)]
1011 unsafe fn pointers(target: NonNull<Self::Target>) -> NonNull<Pointers<Self::Target>> {
1012 unsafe { NonNull::new_unchecked(&raw mut (*target.as_ptr()).payload.pointers) }
1014 }
1015}
1016
1017#[pyclass(name = "ReferenceType", module = "weakref")]
1019#[derive(Debug)]
1020pub struct PyWeak {
1021 pointers: Pointers<Py<Self>>,
1022 wr_object: PyAtomic<*mut PyObject>,
1025 callback: UnsafeCell<Option<PyObjectRef>>,
1027 pub(crate) hash: PyAtomic<crate::common::hash::PyHash>,
1028}
1029
1030cfg_select! {
1031 feature = "threading" => {
1032 unsafe impl Send for PyWeak {}
1033 unsafe impl Sync for PyWeak {}
1034 }
1035 _ => {}
1036}
1037
1038impl PyWeak {
1039 pub(crate) fn upgrade(&self) -> Option<PyObjectRef> {
1041 let obj_ptr = self.wr_object.load(Ordering::Acquire);
1042 if obj_ptr.is_null() {
1043 return None;
1044 }
1045
1046 let _lock = weakref_lock::lock(obj_ptr as usize);
1047
1048 let obj_ptr = self.wr_object.load(Ordering::Relaxed);
1050 if obj_ptr.is_null() {
1051 return None;
1052 }
1053
1054 unsafe {
1055 if !(*obj_ptr).0.ref_count.safe_inc() {
1056 return None;
1057 }
1058 Some(PyObjectRef::from_raw(NonNull::new_unchecked(obj_ptr)))
1059 }
1060 }
1061
1062 pub(crate) fn is_dead(&self) -> bool {
1063 self.wr_object.load(Ordering::Acquire).is_null()
1064 }
1065
1066 pub(crate) fn get_callback(&self) -> Option<PyObjectRef> {
1070 let obj_ptr = self.wr_object.load(Ordering::Acquire);
1071 if obj_ptr.is_null() {
1072 return None;
1074 }
1075
1076 let _lock = weakref_lock::lock(obj_ptr as usize);
1077
1078 let obj_ptr = self.wr_object.load(Ordering::Relaxed);
1080 if obj_ptr.is_null() {
1081 return None;
1082 }
1083
1084 let callback = unsafe { &*self.callback.get() };
1086 callback.clone()
1087 }
1088
1089 fn drop_inner(&self) {
1091 let obj_ptr = self.wr_object.load(Ordering::Acquire);
1092 if obj_ptr.is_null() {
1093 return; }
1095
1096 let _lock = weakref_lock::lock(obj_ptr as usize);
1097
1098 let obj_ptr = self.wr_object.load(Ordering::Relaxed);
1100 if obj_ptr.is_null() {
1101 return; }
1103
1104 let obj = unsafe { &*obj_ptr };
1105 let wrl = obj.0.weakref_list_ref().unwrap();
1107
1108 let node_ptr = unsafe { NonNull::new_unchecked(Py::from_payload_ptr(self).cast_mut()) };
1110
1111 unsafe { unlink_weakref(wrl, node_ptr) };
1113
1114 if wrl.generic.load(Ordering::Relaxed) == node_ptr.as_ptr() {
1116 wrl.generic.store(ptr::null_mut(), Ordering::Relaxed);
1117 }
1118
1119 self.wr_object.store(ptr::null_mut(), Ordering::Relaxed);
1121 }
1122}
1123
1124impl Drop for PyWeak {
1125 #[inline(always)]
1126 fn drop(&mut self) {
1127 let me: &Self = self;
1129 me.drop_inner();
1130 }
1131}
1132
1133impl Py<PyWeak> {
1134 #[inline(always)]
1135 pub fn upgrade(&self) -> Option<PyObjectRef> {
1136 PyWeak::upgrade(self)
1137 }
1138}
1139
1140pub(crate) const SHARED_KEYS_MAX_SIZE: usize = 30;
1143
1144#[derive(Debug)]
1145#[repr(C)]
1146pub(crate) struct InstanceDict {
1147 pub(crate) dict: PyAtomicRef<Option<crate::builtins::PyDict>>,
1150 inline_values_valid: PyAtomic<bool>,
1153}
1154
1155const _: () = assert!(core::mem::offset_of!(InstanceDict, dict) == 0);
1156const _: () = assert!(core::mem::offset_of!(ObjExt, dict) == 0);
1157
1158impl From<PyDictRef> for InstanceDict {
1159 #[inline(always)]
1160 fn from(d: PyDictRef) -> Self {
1161 Self::new(d)
1162 }
1163}
1164
1165impl InstanceDict {
1166 #[inline]
1167 pub(crate) fn new(d: PyDictRef) -> Self {
1168 Self::from_opt(Some(d), false)
1169 }
1170
1171 #[inline]
1172 pub(crate) fn from_opt(d: Option<PyDictRef>, inline_values: bool) -> Self {
1173 Self {
1174 dict: PyAtomicRef::from(d),
1175 inline_values_valid: Radium::new(inline_values),
1176 }
1177 }
1178
1179 #[inline]
1180 pub(crate) fn inline_values_valid(&self) -> bool {
1181 self.inline_values_valid.load(Ordering::Relaxed)
1182 }
1183
1184 #[inline]
1185 pub(crate) fn invalidate_inline_values(&self) {
1186 self.inline_values_valid.store(false, Ordering::Relaxed);
1187 }
1188
1189 pub(crate) fn maybe_materialize_inline_values(&self) {
1190 if !self.inline_values_valid() {
1191 return;
1192 }
1193 let overflow = self.with(|d| d.is_some_and(|d| d.__len__() > SHARED_KEYS_MAX_SIZE));
1194 if overflow {
1195 self.invalidate_inline_values();
1196 }
1197 }
1198
1199 #[inline]
1200 pub(crate) fn get(&self) -> Option<PyDictRef> {
1201 self.dict.load_owned()
1202 }
1203
1204 #[inline]
1210 pub(crate) fn with<R>(&self, f: impl FnOnce(Option<&Py<crate::builtins::PyDict>>) -> R) -> R {
1211 let owned = self.get();
1212 f(owned.as_deref())
1213 }
1214
1215 #[inline]
1216 pub(crate) fn set(&self, d: Option<PyDictRef>) {
1217 self.replace(d);
1218 }
1219
1220 #[inline]
1221 pub(crate) fn replace(&self, d: Option<PyDictRef>) -> Option<PyDictRef> {
1222 self.dict.store(d)
1223 }
1224
1225 pub(crate) fn get_or_insert(&self, vm: &VirtualMachine) -> PyDictRef {
1226 loop {
1227 if let Some(existing) = self.get() {
1228 return existing;
1229 }
1230 let dict = vm.ctx.new_dict();
1231 match self.dict.compare_exchange_empty(dict.clone()) {
1232 Ok(()) => return dict,
1233 Err(rejected) => {
1234 drop(rejected);
1235 drop(dict);
1236 }
1237 }
1238 }
1239 }
1240}
1241
1242impl<T: PyPayload> Py<T> {
1243 unsafe fn drop_fields(ptr: *mut Self) {
1251 unsafe {
1252 core::ptr::drop_in_place(&raw mut (*ptr).payload);
1253 core::ptr::drop_in_place(&raw mut (*ptr).typ);
1254 }
1255 }
1256
1257 unsafe fn dealloc(ptr: *mut Self) {
1263 unsafe {
1264 let (flags, member_count) = (*ptr).read_type_flags();
1265 let has_ext = flags.contains(&crate::types::PyTypeFlags::HAS_DICT) || member_count > 0;
1266 let has_weakref = flags.contains(&crate::types::PyTypeFlags::HAS_WEAKREF);
1267 let published = (*ptr).ref_count.is_published();
1270
1271 if has_ext || has_weakref {
1272 let mut layout = core::alloc::Layout::from_size_align(0, 1).unwrap();
1274
1275 if has_weakref {
1276 layout = layout
1277 .extend(core::alloc::Layout::new::<WeakRefList>())
1278 .unwrap()
1279 .0;
1280 }
1281 if let Some(region) = slot_region_layout(member_count) {
1282 layout = layout.extend(region).unwrap().0;
1283 }
1284 if has_ext {
1285 layout = layout
1286 .extend(core::alloc::Layout::new::<ObjExt>())
1287 .unwrap()
1288 .0;
1289 }
1290 let (combined, inner_offset) =
1291 layout.extend(core::alloc::Layout::new::<Self>()).unwrap();
1292 let combined = combined.pad_to_align();
1293
1294 let alloc_ptr = (ptr as *mut u8).sub(inner_offset);
1295
1296 Self::drop_fields(ptr);
1297
1298 let mut cursor = alloc_ptr;
1300 if has_weakref {
1301 cursor = cursor.add(core::mem::size_of::<WeakRefList>());
1302 }
1303 if let Some(region) = slot_region_layout(member_count) {
1304 let cell = core::mem::size_of::<PyAtomicRef<Option<PyObject>>>();
1305 let first = cursor.add(region.size() - member_count * cell);
1306 let cells = first.cast::<PyAtomicRef<Option<PyObject>>>();
1307 for i in 0..member_count {
1308 core::ptr::drop_in_place(cells.add(i));
1309 }
1310 cursor = cursor.add(region.size());
1311 }
1312 if has_ext {
1313 core::ptr::drop_in_place(cursor.cast::<ObjExt>());
1314 }
1315 if published {
1318 crate::object::qsbr::free_delayed(alloc_ptr, combined);
1319 } else {
1320 alloc::alloc::dealloc(alloc_ptr, combined);
1321 }
1322 } else if published {
1323 let layout = core::alloc::Layout::new::<Self>();
1324 Self::drop_fields(ptr);
1325 crate::object::qsbr::free_delayed(ptr as *mut u8, layout);
1326 } else {
1327 Self::drop_fields(ptr);
1328 drop(Box::from_raw(ptr.cast::<core::mem::MaybeUninit<Self>>()));
1331 }
1332 }
1333 }
1334}
1335
1336impl<T: PyPayload + core::fmt::Debug> Py<T> {
1337 fn new(payload: T, typ: PyTypeRef, dict: Option<PyDictRef>) -> *mut Self {
1341 let member_count = typ.slots.member_count;
1342 let needs_ext = typ
1343 .slots
1344 .flags
1345 .has_feature(crate::types::PyTypeFlags::HAS_DICT)
1346 || member_count > 0;
1347 let needs_weakref = typ
1348 .slots
1349 .flags
1350 .has_feature(crate::types::PyTypeFlags::HAS_WEAKREF);
1351 debug_assert!(
1352 needs_ext || dict.is_none(),
1353 "dict passed to type '{}' without HAS_DICT flag",
1354 typ.name()
1355 );
1356
1357 if needs_ext || needs_weakref {
1358 let mut layout = core::alloc::Layout::from_size_align(0, 1).unwrap();
1360
1361 let weakref_start = if needs_weakref {
1362 let (combined, offset) = layout
1363 .extend(core::alloc::Layout::new::<WeakRefList>())
1364 .unwrap();
1365 layout = combined;
1366 Some(offset)
1367 } else {
1368 None
1369 };
1370
1371 let slots_start = if let Some(region) = slot_region_layout(member_count) {
1372 let (combined, offset) = layout.extend(region).unwrap();
1373 layout = combined;
1374 Some(offset)
1375 } else {
1376 None
1377 };
1378
1379 let ext_start = if needs_ext {
1380 let (combined, offset) =
1381 layout.extend(core::alloc::Layout::new::<ObjExt>()).unwrap();
1382 layout = combined;
1383 Some(offset)
1384 } else {
1385 None
1386 };
1387
1388 let (combined, inner_offset) =
1389 layout.extend(core::alloc::Layout::new::<Self>()).unwrap();
1390 let combined = combined.pad_to_align();
1391
1392 let alloc_ptr = unsafe { alloc::alloc::alloc(combined) };
1393 if alloc_ptr.is_null() {
1394 alloc::alloc::handle_alloc_error(combined);
1395 }
1396 alloc_ptr.expose_provenance();
1398
1399 unsafe {
1400 if let Some(offset) = slots_start {
1401 let region = slot_region_layout(member_count).unwrap();
1402 let cell = core::mem::size_of::<PyAtomicRef<Option<PyObject>>>();
1403 let first = alloc_ptr.add(offset + region.size() - member_count * cell);
1404 let cells = first.cast::<PyAtomicRef<Option<PyObject>>>();
1405 for i in 0..member_count {
1406 cells
1407 .add(i)
1408 .write(PyAtomicRef::<Option<PyObject>>::new_empty());
1409 }
1410 }
1411
1412 if let Some(offset) = ext_start {
1413 let ext_ptr = alloc_ptr.add(offset) as *mut ObjExt;
1414 let has_dict = typ
1415 .slots
1416 .flags
1417 .has_feature(crate::types::PyTypeFlags::HAS_DICT);
1418 let inline_values = typ
1419 .slots
1420 .flags
1421 .has_feature(crate::types::PyTypeFlags::INLINE_VALUES);
1422 ext_ptr.write(ObjExt::new(dict, has_dict, inline_values));
1423 }
1424
1425 if let Some(offset) = weakref_start {
1426 let weakref_ptr = alloc_ptr.add(offset) as *mut WeakRefList;
1427 weakref_ptr.write(WeakRefList::new());
1428 }
1429
1430 let inner_ptr = alloc_ptr.add(inner_offset) as *mut Self;
1431 inner_ptr.write(Self {
1432 ref_count: RefCount::new(),
1433 vtable: PyObjVTable::of::<T>(),
1434 gc_bits: Radium::new(0),
1435 gc_generation: Radium::new(GC_UNTRACKED),
1436 gc_owner: Radium::new(GC_NO_OWNER),
1437 gc_refs: Radium::new(0),
1438 gc_pointers: Pointers::new(),
1439 typ: PyAtomicRef::from(typ),
1440 payload,
1441 });
1442 inner_ptr
1443 }
1444 } else {
1445 Box::into_raw(Box::new(Self {
1446 ref_count: RefCount::new(),
1447 vtable: PyObjVTable::of::<T>(),
1448 gc_bits: Radium::new(0),
1449 gc_generation: Radium::new(GC_UNTRACKED),
1450 gc_owner: Radium::new(GC_NO_OWNER),
1451 gc_refs: Radium::new(0),
1452 gc_pointers: Pointers::new(),
1453 typ: PyAtomicRef::from(typ),
1454 payload,
1455 }))
1456 }
1457 }
1458}
1459
1460pub(crate) struct FreeList<T: PyPayload> {
1466 items: Vec<*mut PyObject>,
1467 _marker: core::marker::PhantomData<T>,
1468}
1469
1470impl<T: PyPayload> FreeList<T> {
1471 pub(crate) const fn new() -> Self {
1472 Self {
1473 items: Vec::new(),
1474 _marker: core::marker::PhantomData,
1475 }
1476 }
1477}
1478
1479impl<T: PyPayload> Default for FreeList<T> {
1480 fn default() -> Self {
1481 Self::new()
1482 }
1483}
1484
1485impl<T: PyPayload> Drop for FreeList<T> {
1486 fn drop(&mut self) {
1487 for ptr in self.items.drain(..) {
1494 unsafe {
1495 alloc::alloc::dealloc(ptr as *mut u8, core::alloc::Layout::new::<Py<T>>());
1496 }
1497 }
1498 }
1499}
1500
1501impl<T: PyPayload> core::ops::Deref for FreeList<T> {
1502 type Target = Vec<*mut PyObject>;
1503 fn deref(&self) -> &Self::Target {
1504 &self.items
1505 }
1506}
1507
1508impl<T: PyPayload> core::ops::DerefMut for FreeList<T> {
1509 fn deref_mut(&mut self) -> &mut Self::Target {
1510 &mut self.items
1511 }
1512}
1513
1514#[repr(transparent)]
1520pub struct PyObjectRef {
1521 ptr: NonNull<PyObject>,
1522}
1523
1524impl Clone for PyObjectRef {
1525 #[inline(always)]
1526 fn clone(&self) -> Self {
1527 (**self).to_owned()
1528 }
1529}
1530
1531cfg_select! {
1532 feature = "threading" => {
1533 unsafe impl Send for PyObjectRef {}
1534 unsafe impl Sync for PyObjectRef {}
1535 }
1536 _ => {}
1537}
1538
1539#[repr(transparent)]
1540pub struct PyObject(Py<Erased>);
1541
1542impl Deref for PyObjectRef {
1543 type Target = PyObject;
1544
1545 #[inline(always)]
1546 fn deref(&self) -> &PyObject {
1547 unsafe { self.ptr.as_ref() }
1548 }
1549}
1550
1551impl ToOwned for PyObject {
1552 type Owned = PyObjectRef;
1553
1554 #[inline(always)]
1555 fn to_owned(&self) -> Self::Owned {
1556 self.0.ref_count.inc();
1557 PyObjectRef {
1558 ptr: NonNull::from(self),
1559 }
1560 }
1561}
1562
1563impl PyObject {
1564 #[inline]
1569 pub fn try_to_owned(&self) -> Option<PyObjectRef> {
1570 if self.0.ref_count.safe_inc() {
1571 Some(PyObjectRef {
1572 ptr: NonNull::from(self),
1573 })
1574 } else {
1575 None
1576 }
1577 }
1578
1579 #[inline]
1590 pub unsafe fn try_to_owned_from_ptr(ptr: *mut Self) -> Option<PyObjectRef> {
1591 let inner = ptr.cast::<Py<Erased>>();
1592 let ref_count = unsafe { &*core::ptr::addr_of!((*inner).ref_count) };
1593 if ref_count.safe_inc() {
1594 Some(PyObjectRef {
1595 ptr: unsafe { NonNull::new_unchecked(ptr) },
1596 })
1597 } else {
1598 None
1599 }
1600 }
1601
1602 pub(crate) fn mark_cache_published(&self) {
1606 self.0.ref_count.mark_published();
1607 }
1608}
1609
1610impl PyObjectRef {
1611 #[inline(always)]
1612 #[must_use]
1613 pub const fn into_raw(self) -> NonNull<PyObject> {
1614 let ptr = self.ptr;
1615 core::mem::forget(self);
1616 ptr
1617 }
1618
1619 #[inline(always)]
1625 #[must_use]
1626 pub const unsafe fn from_raw(ptr: NonNull<PyObject>) -> Self {
1627 Self { ptr }
1628 }
1629
1630 #[inline(always)]
1635 pub fn downcast<T: PyPayload>(self) -> Result<PyRef<T>, Self> {
1636 if self.downcastable::<T>() {
1637 Ok(unsafe { self.downcast_unchecked() })
1638 } else {
1639 Err(self)
1640 }
1641 }
1642
1643 pub fn try_downcast<T: PyPayload>(self, vm: &VirtualMachine) -> PyResult<PyRef<T>> {
1644 T::try_downcast_from(&self, vm)?;
1645 Ok(unsafe { self.downcast_unchecked() })
1646 }
1647
1648 #[inline(always)]
1653 #[must_use]
1654 pub unsafe fn downcast_unchecked<T>(self) -> PyRef<T> {
1655 let obj = ManuallyDrop::new(self);
1658 PyRef {
1659 ptr: obj.ptr.cast(),
1660 }
1661 }
1662
1663 #[inline]
1670 pub fn downcast_exact<T: PyPayload>(self, vm: &VirtualMachine) -> Result<PyRefExact<T>, Self> {
1671 if self.class().is(T::class(&vm.ctx)) {
1672 assert!(
1674 self.downcastable::<T>(),
1675 "obj.__class__ is T::class() but payload is not T"
1676 );
1677 Ok(unsafe { PyRefExact::new_unchecked(PyRef::from_obj_unchecked(self)) })
1679 } else {
1680 Err(self)
1681 }
1682 }
1683}
1684
1685impl PyObject {
1686 #[inline(always)]
1690 fn weak_ref_list(&self) -> Option<&WeakRefList> {
1691 self.0.weakref_list_ref()
1692 }
1693
1694 pub(crate) fn get_weakrefs(&self) -> Option<PyObjectRef> {
1696 let wrl = self.weak_ref_list()?;
1697 let _lock = weakref_lock::lock(self as *const Self as usize);
1698 let head_ptr = wrl.head.load(Ordering::Relaxed);
1699 if head_ptr.is_null() {
1700 None
1701 } else {
1702 let head = unsafe { &*head_ptr };
1703 if head.ref_count.safe_inc() {
1704 Some(unsafe { PyRef::from_raw(head_ptr) }.into())
1705 } else {
1706 None
1707 }
1708 }
1709 }
1710
1711 pub(crate) fn downgrade_with_weakref_typ_opt(
1712 &self,
1713 callback: Option<PyObjectRef>,
1714 typ: PyTypeRef,
1716 ) -> Option<PyRef<PyWeak>> {
1717 self.weak_ref_list()
1718 .map(|wrl| wrl.add(self, typ, true, false, callback, None))
1719 }
1720
1721 pub(crate) fn downgrade_with_typ(
1722 &self,
1723 callback: Option<PyObjectRef>,
1724 typ: PyTypeRef,
1725 vm: &VirtualMachine,
1726 ) -> PyResult<PyRef<PyWeak>> {
1727 if !self
1729 .class()
1730 .slots
1731 .flags
1732 .has_feature(crate::types::PyTypeFlags::HAS_WEAKREF)
1733 {
1734 return Err(vm.new_type_error(format!(
1735 "cannot create weak reference to '{}' object",
1736 self.class().name()
1737 )));
1738 }
1739 let dict = if typ
1740 .slots
1741 .flags
1742 .has_feature(crate::types::PyTypeFlags::HAS_DICT)
1743 {
1744 Some(vm.ctx.new_dict())
1745 } else {
1746 None
1747 };
1748 let cls_is_weakref = typ.is(vm.ctx.types.weakref_type);
1749 let cls_is_weakproxy =
1750 typ.is(vm.ctx.types.weakproxy_type) || typ.is(vm.ctx.types.weakcallableproxy_type);
1751 let wrl = self.weak_ref_list().ok_or_else(|| {
1752 vm.new_type_error(format!(
1753 "cannot create weak reference to '{}' object",
1754 self.class().name()
1755 ))
1756 })?;
1757 Ok(wrl.add(self, typ, cls_is_weakref, cls_is_weakproxy, callback, dict))
1758 }
1759
1760 pub fn downgrade(
1761 &self,
1762 callback: Option<PyObjectRef>,
1763 vm: &VirtualMachine,
1764 ) -> PyResult<PyRef<PyWeak>> {
1765 self.downgrade_with_typ(callback, vm.ctx.types.weakref_type.to_owned(), vm)
1766 }
1767
1768 pub fn clear_weak_refs(&self) {
1769 if let Some(wrl) = self.weak_ref_list() {
1770 wrl.clear(self);
1771 }
1772 }
1773
1774 pub fn get_weak_references(&self) -> Option<Vec<PyRef<PyWeak>>> {
1775 self.weak_ref_list()
1776 .map(|wrl| wrl.get_weak_references(self))
1777 }
1778
1779 #[deprecated(note = "use downcastable instead")]
1780 #[inline(always)]
1781 pub fn payload_is<T: PyPayload>(&self) -> bool {
1782 self.0.vtable.typeid == T::PAYLOAD_TYPE_ID
1783 }
1784
1785 #[deprecated(note = "use downcast_unchecked_ref instead")]
1790 #[inline(always)]
1791 pub const unsafe fn payload_unchecked<T: PyPayload>(&self) -> &T {
1792 let inner = unsafe { &*(&self.0 as *const Py<Erased> as *const Py<T>) };
1795 &inner.payload
1796 }
1797
1798 #[deprecated(note = "use downcast_ref instead")]
1799 #[inline(always)]
1800 pub fn payload<T: PyPayload>(&self) -> Option<&T> {
1801 #[allow(deprecated)]
1802 if self.payload_is::<T>() {
1803 #[allow(deprecated)]
1804 Some(unsafe { self.payload_unchecked() })
1805 } else {
1806 None
1807 }
1808 }
1809
1810 #[inline(always)]
1811 pub fn class(&self) -> &Py<PyType> {
1812 &self.0.typ
1813 }
1814
1815 pub fn set_class(&self, typ: PyTypeRef, vm: &VirtualMachine) {
1816 self.0.typ.swap_to_temporary_refs(typ, vm);
1817 }
1818
1819 #[deprecated(note = "use downcast_ref_if_exact instead")]
1820 #[inline(always)]
1821 pub fn payload_if_exact<T: PyPayload>(&self, vm: &VirtualMachine) -> Option<&T> {
1822 if self.class().is(T::class(&vm.ctx)) {
1823 #[allow(deprecated)]
1824 self.payload()
1825 } else {
1826 None
1827 }
1828 }
1829
1830 #[inline(always)]
1831 pub(crate) fn instance_dict(&self) -> Option<&InstanceDict> {
1832 let ext = self.0.ext_ref()?;
1833 let (flags, _) = self.0.read_type_flags();
1834 if flags.contains(&crate::types::PyTypeFlags::HAS_DICT) {
1835 Some(&ext.dict)
1836 } else {
1837 None
1838 }
1839 }
1840
1841 #[inline]
1843 pub fn has_inline_values(&self) -> bool {
1844 self.class()
1845 .slots
1846 .flags
1847 .has_feature(crate::types::PyTypeFlags::INLINE_VALUES)
1848 && self
1849 .instance_dict()
1850 .is_some_and(InstanceDict::inline_values_valid)
1851 }
1852
1853 #[inline(always)]
1854 pub fn dict(&self) -> Option<PyDictRef> {
1855 self.instance_dict().and_then(|d| d.get())
1856 }
1857
1858 #[inline(always)]
1863 pub fn has_instance_dict(&self) -> bool {
1864 self.instance_dict()
1865 .is_some_and(|d| d.with(|dict| dict.is_some()))
1866 }
1867
1868 #[inline(always)]
1870 pub(crate) fn with_instance_dict<R>(
1871 &self,
1872 f: impl FnOnce(Option<&Py<crate::builtins::PyDict>>) -> R,
1873 ) -> R {
1874 match self.instance_dict() {
1875 Some(d) => d.with(f),
1876 None => f(None),
1877 }
1878 }
1879
1880 pub fn set_dict(&self, dict: Option<PyDictRef>) -> Result<(), Option<PyDictRef>> {
1883 match self.instance_dict() {
1884 Some(d) => {
1885 d.set(dict);
1886 Ok(())
1887 }
1888 None => Err(dict),
1889 }
1890 }
1891
1892 #[deprecated(note = "use downcast_ref instead")]
1893 #[inline(always)]
1894 pub fn payload_if_subclass<T: crate::PyPayload>(&self, vm: &VirtualMachine) -> Option<&T> {
1895 if self.class().fast_issubclass(T::class(&vm.ctx)) {
1896 #[allow(deprecated)]
1897 self.payload()
1898 } else {
1899 None
1900 }
1901 }
1902
1903 #[inline]
1904 pub(crate) fn typeid(&self) -> TypeId {
1905 self.0.vtable.typeid
1906 }
1907
1908 #[inline(always)]
1910 pub fn downcastable<T: PyPayload>(&self) -> bool {
1911 self.typeid() == T::PAYLOAD_TYPE_ID && unsafe { T::validate_downcastable_from(self) }
1912 }
1913
1914 pub fn try_downcast_ref<'a, T: PyPayload>(
1916 &'a self,
1917 vm: &VirtualMachine,
1918 ) -> PyResult<&'a Py<T>> {
1919 T::try_downcast_from(self, vm)?;
1920 Ok(unsafe { self.downcast_unchecked_ref::<T>() })
1921 }
1922
1923 #[inline(always)]
1925 pub fn downcast_ref<T: PyPayload>(&self) -> Option<&Py<T>> {
1926 if self.downcastable::<T>() {
1927 Some(unsafe { self.downcast_unchecked_ref::<T>() })
1930 } else {
1931 None
1932 }
1933 }
1934
1935 #[inline(always)]
1936 pub fn downcast_ref_if_exact<T: PyPayload>(&self, vm: &VirtualMachine) -> Option<&Py<T>> {
1937 self.class()
1938 .is(T::class(&vm.ctx))
1939 .then(|| unsafe { self.downcast_unchecked_ref::<T>() })
1940 }
1941
1942 #[inline(always)]
1945 pub unsafe fn downcast_unchecked_ref<T: PyPayload>(&self) -> &Py<T> {
1946 debug_assert!(self.downcastable::<T>());
1947 unsafe { &*(self as *const Self as *const Py<T>) }
1949 }
1950
1951 #[inline(always)]
1952 pub fn strong_count(&self) -> usize {
1953 self.0.ref_count.get()
1954 }
1955
1956 #[inline]
1957 pub fn weak_count(&self) -> Option<usize> {
1958 self.weak_ref_list().map(|wrl| wrl.count(self))
1959 }
1960
1961 #[inline(always)]
1962 pub const fn as_raw(&self) -> *const Self {
1963 self
1964 }
1965
1966 #[inline]
1969 pub fn gc_finalized(&self) -> bool {
1970 GcBits::from_bits_retain(self.0.gc_bits.load(Ordering::Relaxed)).contains(GcBits::FINALIZED)
1971 }
1972
1973 #[inline]
1976 pub(crate) fn set_gc_finalized(&self) {
1977 self.set_gc_bit(GcBits::FINALIZED);
1978 }
1979
1980 #[inline]
1982 pub(crate) fn set_gc_bit(&self, bit: GcBits) {
1983 self.0.gc_bits.fetch_or(bit.bits(), Ordering::Relaxed);
1984 }
1985
1986 #[inline]
1988 pub(crate) fn gc_generation(&self) -> u8 {
1989 self.0.gc_generation.load(Ordering::Relaxed)
1990 }
1991
1992 #[inline]
1995 pub(crate) fn set_gc_generation(&self, generation: u8) {
1996 self.0.gc_generation.store(generation, Ordering::Relaxed);
1997 }
1998
1999 #[inline]
2001 pub(crate) fn gc_owner(&self) -> GcOwner {
2002 self.0.gc_owner.load(Ordering::Relaxed)
2003 }
2004
2005 #[inline]
2009 pub(crate) fn set_gc_owner(&self, owner: GcOwner) {
2010 self.0.gc_owner.store(owner, Ordering::Relaxed);
2011 }
2012
2013 #[inline]
2018 pub(crate) fn start_gc_refs(&self, strong_count: usize) {
2019 let refs = if strong_count >= GC_REACHABLE as usize {
2020 GC_REACHABLE
2021 } else {
2022 strong_count as u32
2023 };
2024 self.0.gc_refs.store(refs, Ordering::Relaxed);
2025 self.set_gc_bit(GcBits::COLLECTING);
2026 }
2027
2028 #[inline]
2030 pub(crate) fn gc_refs(&self) -> u32 {
2031 self.0.gc_refs.load(Ordering::Relaxed)
2032 }
2033
2034 #[inline]
2036 pub(crate) fn is_gc_collecting(&self) -> bool {
2037 GcBits::from_bits_retain(self.0.gc_bits.load(Ordering::Relaxed))
2038 .contains(GcBits::COLLECTING)
2039 }
2040
2041 #[inline]
2045 pub(crate) fn subtract_gc_ref(&self) {
2046 let refs = self.0.gc_refs.load(Ordering::Relaxed);
2047 if refs == GC_REACHABLE {
2048 return;
2049 }
2050 self.0
2051 .gc_refs
2052 .store(refs.saturating_sub(1), Ordering::Relaxed);
2053 }
2054
2055 #[inline]
2058 pub(crate) fn mark_gc_reachable(&self) -> bool {
2059 if self.0.gc_refs.load(Ordering::Relaxed) == GC_REACHABLE {
2060 return false;
2061 }
2062 self.0.gc_refs.store(GC_REACHABLE, Ordering::Relaxed);
2063 true
2064 }
2065
2066 #[inline]
2068 pub(crate) fn end_gc_refs(&self) {
2069 self.0
2070 .gc_bits
2071 .fetch_and(!GcBits::COLLECTING.bits(), Ordering::Relaxed);
2072 }
2073
2074 #[inline]
2076 pub(crate) fn set_gc_tracked(&self) {
2077 self.set_gc_bit(GcBits::TRACKED);
2078 }
2079
2080 #[inline]
2090 pub(crate) fn init_gc_tracked_bit(&self) {
2091 debug_assert_eq!(
2092 self.0.gc_bits.load(Ordering::Relaxed),
2093 0,
2094 "init_gc_tracked_bit called on an object with non-zero gc_bits"
2095 );
2096 self.0
2097 .gc_bits
2098 .store(GcBits::TRACKED.bits(), Ordering::Relaxed);
2099 }
2100
2101 #[inline]
2103 pub(crate) fn clear_gc_tracked(&self) {
2104 self.0
2105 .gc_bits
2106 .fetch_and(!GcBits::TRACKED.bits(), Ordering::Relaxed);
2107 }
2108
2109 #[inline(always)] fn drop_slow_inner(&self) -> Result<(), ()> {
2111 #[inline(never)]
2113 #[cold]
2114 fn call_slot_del(
2115 zelf: &PyObject,
2116 slot_del: fn(&PyObject, &VirtualMachine) -> PyResult<()>,
2117 ) -> Result<(), ()> {
2118 let ret = crate::vm::thread::with_vm(zelf, |vm| {
2119 zelf.0.ref_count.inc_by(2);
2122
2123 let del_method = zelf.get_class_attr(identifier!(vm, __del__)).unwrap();
2124 if let Err(e) = slot_del(zelf, vm) {
2125 let msg = del_method
2126 .repr(vm)
2127 .ok()
2128 .map(|r| format!("Exception ignored while calling deallocator {r}"));
2129 vm.run_unraisable(e, msg, del_method);
2130 }
2131
2132 let _ = zelf.0.ref_count.dec();
2136 zelf.0.ref_count.dec()
2137 });
2138 match ret {
2139 Some(true) => Ok(()),
2141 Some(false) => Err(()),
2143 None => Ok(()),
2144 }
2145 }
2146
2147 let del = self.class().slots().del.load();
2151 if let Some(slot_del) = del
2152 && !self.gc_finalized()
2153 {
2154 let needs_del = self
2159 .class()
2160 .slots
2161 .del_needed
2162 .load()
2163 .is_none_or(|check| check(self));
2164 if needs_del {
2165 self.set_gc_finalized();
2166 call_slot_del(self, slot_del)?;
2167 }
2168 }
2169
2170 if let Some(wrl) = self.weak_ref_list() {
2175 wrl.clear(self);
2176 }
2177
2178 Ok(())
2179 }
2180
2181 #[inline(never)]
2183 unsafe fn drop_slow(ptr: NonNull<Self>) {
2184 let dealloc = unsafe { ptr.as_ref().0.vtable.dealloc };
2185 unsafe { dealloc(ptr.as_ptr()) }
2186 }
2187
2188 pub(crate) unsafe fn mark_intern(&self) {
2193 self.0.ref_count.leak();
2194 }
2195
2196 pub(crate) fn is_interned(&self) -> bool {
2197 self.0.ref_count.is_leaked()
2198 }
2199
2200 #[inline]
2221 pub fn make_immortal(&self) {
2222 self.0.ref_count.make_immortal();
2223 }
2224
2225 #[inline(always)]
2227 #[must_use]
2228 pub fn is_immortal(&self) -> bool {
2229 self.0.ref_count.is_immortal()
2230 }
2231
2232 pub(crate) fn get_slot(&self, byte_offset: isize) -> Option<PyObjectRef> {
2233 self.slot_cell_at(byte_offset).load_owned()
2234 }
2235
2236 pub(crate) fn set_slot(&self, byte_offset: isize, value: Option<PyObjectRef>) {
2237 drop(self.slot_cell_at(byte_offset).store(value));
2238 }
2239
2240 fn slot_cell_at(&self, byte_offset: isize) -> &PyAtomicRef<Option<Self>> {
2241 let addr = (self as *const Self as *const u8)
2242 .addr()
2243 .wrapping_add(byte_offset as usize);
2244 let ptr = core::ptr::with_exposed_provenance::<PyAtomicRef<Option<Self>>>(addr);
2245 unsafe { &*ptr }
2248 }
2249
2250 pub fn is_gc_tracked(&self) -> bool {
2252 GcBits::from_bits_retain(self.0.gc_bits.load(Ordering::Relaxed)).contains(GcBits::TRACKED)
2253 }
2254
2255 pub fn gc_get_referents(&self) -> Vec<PyObjectRef> {
2258 let mut result = Vec::new();
2259 self.0.traverse(&mut |child: &Self| {
2260 result.push(child.to_owned());
2261 });
2262 result
2263 }
2264
2265 pub fn try_call_finalizer(&self) {
2270 let del = self.class().slots().del.load();
2271 if let Some(slot_del) = del
2272 && !self.gc_finalized()
2273 && self
2274 .class()
2275 .slots
2276 .del_needed
2277 .load()
2278 .is_none_or(|check| check(self))
2279 {
2280 self.set_gc_finalized();
2283 let result = crate::vm::thread::with_vm(self, |vm| {
2284 if let Err(e) = slot_del(self, vm)
2285 && let Some(del_method) = self.get_class_attr(identifier!(vm, __del__))
2286 {
2287 let msg = del_method
2288 .repr(vm)
2289 .ok()
2290 .map(|r| format!("Exception ignored while calling deallocator {r}"));
2291 vm.run_unraisable(e, msg, del_method);
2292 }
2293 });
2294 let _ = result;
2295 }
2296 }
2297
2298 pub fn gc_clear_weakrefs_collect_callbacks(&self) -> Vec<(PyRef<PyWeak>, PyObjectRef)> {
2303 if let Some(wrl) = self.weak_ref_list() {
2304 wrl.clear_for_gc_collect_callbacks(self)
2305 } else {
2306 vec![]
2307 }
2308 }
2309
2310 pub unsafe fn gc_get_referent_ptrs(&self) -> Vec<NonNull<Self>> {
2318 let mut result = Vec::new();
2319 unsafe { self.gc_extend_referent_ptrs(&mut result) };
2320 result
2321 }
2322
2323 pub unsafe fn gc_extend_referent_ptrs(&self, out: &mut Vec<NonNull<Self>>) {
2329 self.0.traverse(&mut |child: &Self| {
2331 out.push(NonNull::from(child));
2332 });
2333 }
2334
2335 pub unsafe fn gc_clear_raw(ptr: *mut Self) -> Vec<PyObjectRef> {
2344 let mut result = Vec::new();
2345 let obj = unsafe { &*ptr };
2346
2347 if let Some(clear_fn) = obj.0.vtable.clear {
2349 unsafe { clear_fn(ptr, &mut result) };
2350 }
2351
2352 let (flags, member_count) = obj.0.read_type_flags();
2357 let has_ext = flags.contains(&crate::types::PyTypeFlags::HAS_DICT) || member_count > 0;
2358 if has_ext {
2359 let self_addr = (ptr as *const u8).addr();
2360 let ext_ptr = core::ptr::with_exposed_provenance_mut::<ObjExt>(
2361 self_addr.wrapping_sub(EXT_OFFSET),
2362 );
2363 let ext = unsafe { &mut *ext_ptr };
2364 if flags.contains(&crate::types::PyTypeFlags::HAS_DICT)
2365 && let Some(dict_ref) = ext.dict.replace(None)
2366 {
2367 result.push(dict_ref.into());
2368 }
2369 for slot in obj.0.slot_cells() {
2370 if let Some(val) = slot.store(None) {
2371 result.push(val);
2372 }
2373 }
2374 }
2375
2376 result
2377 }
2378
2379 pub unsafe fn gc_clear(&self) -> Vec<PyObjectRef> {
2387 unsafe { Self::gc_clear_raw(self as *const _ as *mut Self) }
2391 }
2392
2393 pub fn gc_has_clear(&self) -> bool {
2396 self.0.vtable.clear.is_some()
2397 || self
2398 .0
2399 .read_type_flags()
2400 .0
2401 .contains(&crate::types::PyTypeFlags::HAS_DICT)
2402 || self.0.read_type_flags().1 > 0
2403 }
2404}
2405
2406impl Borrow<PyObject> for PyObjectRef {
2407 #[inline(always)]
2408 fn borrow(&self) -> &PyObject {
2409 self
2410 }
2411}
2412
2413impl AsRef<PyObject> for PyObjectRef {
2414 #[inline(always)]
2415 fn as_ref(&self) -> &PyObject {
2416 self
2417 }
2418}
2419
2420impl<'a, T: PyPayload> From<&'a Py<T>> for &'a PyObject {
2421 #[inline(always)]
2422 fn from(py_ref: &'a Py<T>) -> Self {
2423 py_ref.as_object()
2424 }
2425}
2426
2427impl Drop for PyObjectRef {
2428 #[inline]
2429 fn drop(&mut self) {
2430 if self.0.ref_count.dec() {
2431 unsafe { PyObject::drop_slow(self.ptr) }
2432 }
2433 }
2434}
2435
2436impl fmt::Debug for PyObject {
2437 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2438 unsafe { (self.0.vtable.debug)(self, f) }
2441 }
2442}
2443
2444impl fmt::Debug for PyObjectRef {
2445 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2446 self.as_object().fmt(f)
2447 }
2448}
2449
2450const STACKREF_BORROW_TAG: usize = 1;
2451
2452#[repr(transparent)]
2492pub struct PyStackRef {
2493 bits: NonZeroUsize,
2494}
2495
2496impl PyStackRef {
2497 #[inline(always)]
2500 #[must_use]
2501 pub fn new_owned(obj: PyObjectRef) -> Self {
2502 let ptr = obj.into_raw();
2503 let bits = ptr.as_ptr() as usize;
2504 debug_assert!(
2505 bits & STACKREF_BORROW_TAG == 0,
2506 "PyObject pointer must be aligned"
2507 );
2508 Self {
2509 bits: unsafe { NonZeroUsize::new_unchecked(bits) },
2511 }
2512 }
2513
2514 #[inline(always)]
2522 pub unsafe fn new_borrowed(obj: &PyObject) -> Self {
2523 let bits = (obj as *const PyObject as usize) | STACKREF_BORROW_TAG;
2524 Self {
2525 bits: unsafe { NonZeroUsize::new_unchecked(bits) },
2527 }
2528 }
2529
2530 #[inline(always)]
2532 #[must_use]
2533 pub fn is_borrowed(&self) -> bool {
2534 self.bits.get() & STACKREF_BORROW_TAG != 0
2535 }
2536
2537 #[inline(always)]
2539 #[must_use]
2540 pub fn as_object(&self) -> &PyObject {
2541 unsafe { &*((self.bits.get() & !STACKREF_BORROW_TAG) as *const PyObject) }
2542 }
2543
2544 #[inline(always)]
2549 #[must_use]
2550 pub fn to_pyobj(self) -> PyObjectRef {
2551 let obj = if self.is_borrowed() {
2552 self.as_object().to_owned() } else {
2554 let ptr = unsafe { NonNull::new_unchecked(self.bits.get() as *mut PyObject) };
2555 unsafe { PyObjectRef::from_raw(ptr) }
2556 };
2557 core::mem::forget(self); obj
2559 }
2560
2561 #[inline(always)]
2564 pub fn promote(&mut self) {
2565 if self.is_borrowed() {
2566 self.as_object().0.ref_count.inc();
2567 self.bits =
2569 unsafe { NonZeroUsize::new_unchecked(self.bits.get() & !STACKREF_BORROW_TAG) };
2570 }
2571 }
2572}
2573
2574impl Drop for PyStackRef {
2575 #[inline]
2576 fn drop(&mut self) {
2577 if !self.is_borrowed() {
2578 let ptr = unsafe { NonNull::new_unchecked(self.bits.get() as *mut PyObject) };
2580 drop(unsafe { PyObjectRef::from_raw(ptr) });
2581 }
2582 }
2584}
2585
2586impl core::ops::Deref for PyStackRef {
2587 type Target = PyObject;
2588
2589 #[inline(always)]
2590 fn deref(&self) -> &PyObject {
2591 self.as_object()
2592 }
2593}
2594
2595impl Clone for PyStackRef {
2596 #[inline(always)]
2598 fn clone(&self) -> Self {
2599 Self::new_owned(self.as_object().to_owned())
2600 }
2601}
2602
2603impl fmt::Debug for PyStackRef {
2604 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2605 if self.is_borrowed() {
2606 write!(f, "PyStackRef(borrowed, ")?;
2607 } else {
2608 write!(f, "PyStackRef(owned, ")?;
2609 }
2610 self.as_object().fmt(f)?;
2611 write!(f, ")")
2612 }
2613}
2614
2615cfg_select! {
2616 feature = "threading" => {
2617 unsafe impl Send for PyStackRef {}
2618 unsafe impl Sync for PyStackRef {}
2619 }
2620 _ => {}
2621}
2622
2623const _: () = assert!(
2625 core::mem::size_of::<Option<PyStackRef>>() == core::mem::size_of::<Option<PyObjectRef>>()
2626);
2627const _: () =
2628 assert!(core::mem::size_of::<Option<PyStackRef>>() == core::mem::size_of::<PyStackRef>());
2629
2630impl<T: PyPayload> Py<T> {
2631 pub fn downgrade(
2632 &self,
2633 callback: Option<PyObjectRef>,
2634 vm: &VirtualMachine,
2635 ) -> PyResult<PyWeakRef<T>> {
2636 Ok(PyWeakRef {
2637 weak: self.as_object().downgrade(callback, vm)?,
2638 _marker: PhantomData,
2639 })
2640 }
2641
2642 #[inline]
2643 pub fn payload(&self) -> &T {
2644 &self.payload
2645 }
2646
2647 #[inline]
2654 #[cfg_attr(not(feature = "threading"), allow(dead_code))]
2655 pub(crate) unsafe fn from_payload_ptr(payload: *const T) -> *const Self {
2656 let offset = core::mem::offset_of!(Self, payload);
2657 unsafe { (payload as *const u8).sub(offset) as *const Self }
2658 }
2659}
2660
2661impl<T> ToOwned for Py<T> {
2662 type Owned = PyRef<T>;
2663
2664 #[inline(always)]
2665 fn to_owned(&self) -> Self::Owned {
2666 self.ref_count.inc();
2667 PyRef {
2668 ptr: NonNull::from(self),
2669 }
2670 }
2671}
2672
2673impl<T> Deref for Py<T> {
2674 type Target = T;
2675
2676 #[inline(always)]
2677 fn deref(&self) -> &Self::Target {
2678 &self.payload
2679 }
2680}
2681
2682impl<T: PyPayload> Borrow<PyObject> for Py<T> {
2683 #[inline(always)]
2684 fn borrow(&self) -> &PyObject {
2685 unsafe { &*(self as *const Self as *const PyObject) }
2686 }
2687}
2688
2689impl<T> core::hash::Hash for Py<T>
2690where
2691 T: core::hash::Hash + PyPayload,
2692{
2693 #[inline]
2694 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
2695 self.deref().hash(state)
2696 }
2697}
2698
2699impl<T> PartialEq for Py<T>
2700where
2701 T: PartialEq + PyPayload,
2702{
2703 #[inline]
2704 fn eq(&self, other: &Self) -> bool {
2705 self.deref().eq(&**other)
2706 }
2707}
2708
2709impl<T> Eq for Py<T> where T: Eq + PyPayload {}
2710
2711impl<T> AsRef<PyObject> for Py<T>
2712where
2713 T: PyPayload,
2714{
2715 #[inline(always)]
2716 fn as_ref(&self) -> &PyObject {
2717 self.borrow()
2718 }
2719}
2720
2721impl<T: PyPayload + core::fmt::Debug> fmt::Debug for Py<T> {
2722 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2723 (**self).fmt(f)
2724 }
2725}
2726
2727#[repr(transparent)]
2737pub struct PyRef<T> {
2738 ptr: NonNull<Py<T>>,
2739}
2740
2741cfg_select! {
2742 feature = "threading" => {
2743 unsafe impl<T> Send for PyRef<T> {}
2744 unsafe impl<T> Sync for PyRef<T> {}
2745 }
2746 _ => {}
2747}
2748
2749impl<T: fmt::Debug> fmt::Debug for PyRef<T> {
2750 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2751 (**self).fmt(f)
2752 }
2753}
2754
2755impl<T> Drop for PyRef<T> {
2756 #[inline]
2757 fn drop(&mut self) {
2758 if self.ref_count.dec() {
2759 unsafe { PyObject::drop_slow(self.ptr.cast::<PyObject>()) }
2760 }
2761 }
2762}
2763
2764impl<T> Clone for PyRef<T> {
2765 #[inline(always)]
2766 fn clone(&self) -> Self {
2767 (**self).to_owned()
2768 }
2769}
2770
2771impl<T: PyPayload> PyRef<T> {
2772 #[inline(always)]
2773 pub(super) const fn into_non_null(self) -> NonNull<Py<T>> {
2774 let ptr = self.ptr;
2775 core::mem::forget(self);
2776 ptr
2777 }
2778
2779 #[must_use]
2782 #[inline(always)]
2783 pub const unsafe fn from_non_null(ptr: NonNull<Py<T>>) -> Self {
2784 Self { ptr }
2785 }
2786
2787 #[inline(always)]
2790 pub const unsafe fn from_raw(raw: *const Py<T>) -> Self {
2791 unsafe { Self::from_non_null(NonNull::new_unchecked(raw as *mut _)) }
2792 }
2793
2794 #[inline(always)]
2796 unsafe fn from_obj_unchecked(obj: PyObjectRef) -> Self {
2797 debug_assert!(obj.downcast_ref::<T>().is_some());
2798 let obj = ManuallyDrop::new(obj);
2799 Self {
2800 ptr: obj.ptr.cast(),
2801 }
2802 }
2803
2804 #[must_use]
2805 pub const fn leak(pyref: Self) -> &'static Py<T> {
2806 let ptr = pyref.ptr;
2807 core::mem::forget(pyref);
2808 unsafe { ptr.as_ref() }
2809 }
2810}
2811
2812impl<T: PyPayload + crate::object::MaybeTraverse + core::fmt::Debug> PyRef<T> {
2813 #[inline(always)]
2814 pub fn new_ref(payload: T, typ: crate::builtins::PyTypeRef, dict: Option<PyDictRef>) -> Self {
2815 let has_dict = dict.is_some();
2816 let is_heaptype = typ.heaptype_ext.is_some();
2817
2818 let cached = if !has_dict && !is_heaptype {
2820 unsafe { T::freelist_pop(&payload) }
2821 } else {
2822 None
2823 };
2824
2825 let ptr = if let Some(cached) = cached {
2826 let inner = cached.as_ptr() as *mut Py<T>;
2827 unsafe {
2828 core::ptr::write(&mut (*inner).ref_count, RefCount::new());
2829 (*inner).gc_bits.store(0, Ordering::Relaxed);
2830 core::ptr::drop_in_place(&mut (*inner).payload);
2831 core::ptr::write(&mut (*inner).payload, payload);
2832 let cached_typ: *const Py<PyType> = &*(*inner).typ;
2836 if core::ptr::eq(cached_typ, &*typ) {
2837 drop(typ);
2838 } else {
2839 let _old = (*inner).typ.swap(typ);
2840 }
2841 }
2842 unsafe { NonNull::new_unchecked(inner.cast::<Py<T>>()) }
2843 } else {
2844 let inner = Py::new(payload, typ, dict);
2845 unsafe { NonNull::new_unchecked(inner.cast::<Py<T>>()) }
2846 };
2847
2848 if (<T as crate::object::MaybeTraverse>::HAS_TRAVERSE || has_dict || is_heaptype)
2855 && !T::NEW_REF_UNTRACKED
2856 {
2857 unsafe {
2860 crate::gc_state::track_new_object(ptr.cast());
2861 }
2862 }
2863
2864 Self { ptr }
2865 }
2866}
2867
2868impl<T: crate::class::PySubclass + core::fmt::Debug> PyRef<T>
2869where
2870 T::Base: core::fmt::Debug,
2871{
2872 #[inline]
2876 #[must_use]
2877 pub fn into_base(self) -> PyRef<T::Base> {
2878 let obj: PyObjectRef = self.into();
2879 match obj.downcast() {
2880 Ok(base_ref) => base_ref,
2881 Err(_) => unsafe { core::hint::unreachable_unchecked() },
2882 }
2883 }
2884 #[inline]
2885 #[must_use]
2886 pub fn upcast<U: PyPayload + StaticType>(self) -> PyRef<U>
2887 where
2888 T: StaticType,
2889 {
2890 debug_assert!(T::static_type().is_subtype(U::static_type()));
2891 let obj: PyObjectRef = self.into();
2892 match obj.downcast::<U>() {
2893 Ok(upcast_ref) => upcast_ref,
2894 Err(_) => unsafe { core::hint::unreachable_unchecked() },
2895 }
2896 }
2897}
2898
2899impl<T: crate::class::PySubclass> Py<T> {
2900 #[inline]
2902 pub fn to_base(&self) -> &Py<T::Base> {
2903 debug_assert!(self.as_object().downcast_ref::<T::Base>().is_some());
2904 unsafe { &*(self as *const Self as *const Py<T::Base>) }
2907 }
2908
2909 #[inline]
2911 pub fn upcast_ref<U: PyPayload + StaticType>(&self) -> &Py<U>
2912 where
2913 T: StaticType,
2914 {
2915 debug_assert!(T::static_type().is_subtype(U::static_type()));
2916 unsafe { &*(self as *const Self as *const Py<U>) }
2918 }
2919}
2920
2921impl<T> Borrow<PyObject> for PyRef<T>
2922where
2923 T: PyPayload,
2924{
2925 #[inline(always)]
2926 fn borrow(&self) -> &PyObject {
2927 (**self).as_object()
2928 }
2929}
2930
2931impl<T> AsRef<PyObject> for PyRef<T>
2932where
2933 T: PyPayload,
2934{
2935 #[inline(always)]
2936 fn as_ref(&self) -> &PyObject {
2937 self.borrow()
2938 }
2939}
2940
2941impl<T> From<PyRef<T>> for PyObjectRef {
2942 #[inline]
2943 fn from(value: PyRef<T>) -> Self {
2944 let me = ManuallyDrop::new(value);
2945 Self { ptr: me.ptr.cast() }
2946 }
2947}
2948
2949impl<T> Borrow<Py<T>> for PyRef<T> {
2950 #[inline(always)]
2951 fn borrow(&self) -> &Py<T> {
2952 self
2953 }
2954}
2955
2956impl<T> AsRef<Py<T>> for PyRef<T> {
2957 #[inline(always)]
2958 fn as_ref(&self) -> &Py<T> {
2959 self
2960 }
2961}
2962
2963impl<T> Deref for PyRef<T> {
2964 type Target = Py<T>;
2965
2966 #[inline(always)]
2967 fn deref(&self) -> &Py<T> {
2968 unsafe { self.ptr.as_ref() }
2969 }
2970}
2971
2972impl<T> core::hash::Hash for PyRef<T>
2973where
2974 T: core::hash::Hash + PyPayload,
2975{
2976 #[inline]
2977 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
2978 self.deref().hash(state)
2979 }
2980}
2981
2982impl<T> PartialEq for PyRef<T>
2983where
2984 T: PartialEq + PyPayload,
2985{
2986 #[inline]
2987 fn eq(&self, other: &Self) -> bool {
2988 self.deref().eq(&**other)
2989 }
2990}
2991
2992impl<T> Eq for PyRef<T> where T: Eq + PyPayload {}
2993
2994#[repr(transparent)]
2995pub struct PyWeakRef<T: PyPayload> {
2996 weak: PyRef<PyWeak>,
2997 _marker: PhantomData<T>,
2998}
2999
3000impl<T: PyPayload> PyWeakRef<T> {
3001 #[must_use]
3002 pub fn upgrade(&self) -> Option<PyRef<T>> {
3003 self.weak
3004 .upgrade()
3005 .map(|obj| unsafe { PyRef::from_obj_unchecked(obj) })
3007 }
3008}
3009
3010pub(crate) struct BootstrapTypeHierarchy {
3013 pub type_type: PyTypeRef,
3014 pub object_type: PyTypeRef,
3015 pub tuple_type: PyTypeRef,
3016 pub weakref_type: PyTypeRef,
3017 pub empty_tuple: PyTupleRef,
3018}
3019
3020pub(crate) fn init_type_hierarchy() -> BootstrapTypeHierarchy {
3021 use crate::{
3022 builtins::{object, tuple},
3023 class::PyClassImpl,
3024 };
3025 use core::mem::MaybeUninit;
3026
3027 static_assertions::assert_eq_size!(MaybeUninit<Py<PyType>>, Py<PyType>);
3028 static_assertions::assert_eq_align!(MaybeUninit<Py<PyType>>, Py<PyType>);
3029 static_assertions::assert_eq_size!(MaybeUninit<Py<PyTuple>>, Py<PyTuple>);
3030 static_assertions::assert_eq_align!(MaybeUninit<Py<PyTuple>>, Py<PyTuple>);
3031
3032 let alloc_type_with_prefixes = || -> *mut Py<PyType> {
3036 let inner_layout = core::alloc::Layout::new::<MaybeUninit<Py<PyType>>>();
3037 let ext_layout = core::alloc::Layout::new::<ObjExt>();
3038 let weakref_layout = core::alloc::Layout::new::<WeakRefList>();
3039
3040 let (layout, ext_offset) = weakref_layout.extend(ext_layout).unwrap();
3042 let (combined, inner_offset) = layout.extend(inner_layout).unwrap();
3043 let combined = combined.pad_to_align();
3044
3045 let alloc_ptr = unsafe { alloc::alloc::alloc(combined) };
3046 if alloc_ptr.is_null() {
3047 alloc::alloc::handle_alloc_error(combined);
3048 }
3049 alloc_ptr.expose_provenance();
3050
3051 unsafe {
3052 (alloc_ptr as *mut WeakRefList).write(WeakRefList::new());
3053 (alloc_ptr.add(ext_offset) as *mut ObjExt).write(ObjExt::new(None, true, false));
3054 alloc_ptr.add(inner_offset).cast()
3055 }
3056 };
3057
3058 let alloc_tuple =
3059 || Box::into_raw(Box::new(MaybeUninit::<Py<PyTuple>>::uninit())).cast::<Py<PyTuple>>();
3060
3061 unsafe fn init_ref_count<T>(ptr: *mut Py<T>) {
3062 unsafe { ptr::addr_of_mut!((*ptr).ref_count).write(RefCount::new()) };
3063 }
3064
3065 unsafe fn initial_ref<T: PyPayload>(ptr: *mut Py<T>) -> PyRef<T> {
3066 unsafe { PyRef::from_raw(ptr.cast()) }
3067 }
3068
3069 unsafe fn clone_raw_ref<T: PyPayload>(ptr: *mut Py<T>) -> PyRef<T> {
3070 unsafe { &*ptr::addr_of!((*ptr).ref_count) }.inc();
3071 unsafe { PyRef::from_raw(ptr.cast()) }
3072 }
3073
3074 unsafe fn into_type_tuple(tuple: PyTupleRef) -> PyTypeTupleRef {
3075 unsafe { core::mem::transmute::<PyTupleRef, PyTypeTupleRef>(tuple) }
3078 }
3079
3080 unsafe fn init_inner<T>(ptr: *mut Py<T>, typ: PyTypeRef, payload: T)
3081 where
3082 T: PyPayload + MaybeTraverse + fmt::Debug,
3083 {
3084 unsafe {
3085 ptr::addr_of_mut!((*ptr).vtable).write(PyObjVTable::of::<T>());
3086 ptr::addr_of_mut!((*ptr).gc_bits).write(Radium::new(0));
3087 ptr::addr_of_mut!((*ptr).gc_generation).write(Radium::new(GC_UNTRACKED));
3088 ptr::addr_of_mut!((*ptr).gc_owner).write(Radium::new(GC_NO_OWNER));
3089 ptr::addr_of_mut!((*ptr).gc_refs).write(Radium::new(0));
3090 ptr::addr_of_mut!((*ptr).gc_pointers).write(Pointers::new());
3091 ptr::addr_of_mut!((*ptr).typ).write(PyAtomicRef::from_ref_without_retag(typ));
3092 ptr::addr_of_mut!((*ptr).payload).write(payload);
3093 }
3094 }
3095
3096 let type_type_ptr = alloc_type_with_prefixes();
3097 let object_type_ptr = alloc_type_with_prefixes();
3098 let tuple_type_ptr = alloc_type_with_prefixes();
3099 let empty_tuple_ptr = alloc_tuple();
3100 let type_bases_ptr = alloc_tuple();
3101 let tuple_bases_ptr = alloc_tuple();
3102
3103 unsafe {
3104 init_ref_count(type_type_ptr);
3105 init_ref_count(object_type_ptr);
3106 init_ref_count(tuple_type_ptr);
3107 init_ref_count(empty_tuple_ptr);
3108 init_ref_count(type_bases_ptr);
3109 init_ref_count(tuple_bases_ptr);
3110 }
3111
3112 let type_type = unsafe { initial_ref(type_type_ptr) };
3116 let object_type = unsafe { initial_ref(object_type_ptr) };
3117 let tuple_type = unsafe { initial_ref(tuple_type_ptr) };
3118 let empty_tuple = unsafe { initial_ref(empty_tuple_ptr) };
3119 let type_bases = unsafe { into_type_tuple(initial_ref(type_bases_ptr)) };
3120 let tuple_bases = unsafe { into_type_tuple(initial_ref(tuple_bases_ptr)) };
3121
3122 let type_payload = PyType {
3123 base: unsafe {
3124 PyAtomicRef::from_optional_ref_without_retag(Some(clone_raw_ref(object_type_ptr)))
3125 },
3126 bases: PyRwLock::new(type_bases),
3127 mro: PyRwLock::new(vec![unsafe { clone_raw_ref(type_type_ptr) }, unsafe {
3128 clone_raw_ref(object_type_ptr)
3129 }]),
3130 subclasses: PyRwLock::default(),
3131 attributes: Default::default(),
3132 slots: PyType::make_slots(),
3133 heaptype_ext: None,
3134 tp_version_tag: core::sync::atomic::AtomicU32::new(0),
3135 };
3136 let object_payload = PyType {
3137 base: unsafe { PyAtomicRef::from_optional_ref_without_retag(None) },
3138 bases: PyRwLock::new(unsafe { into_type_tuple(clone_raw_ref(empty_tuple_ptr)) }),
3139 mro: PyRwLock::new(vec![unsafe { clone_raw_ref(object_type_ptr) }]),
3140 subclasses: PyRwLock::default(),
3141 attributes: Default::default(),
3142 slots: object::PyBaseObject::make_slots(),
3143 heaptype_ext: None,
3144 tp_version_tag: core::sync::atomic::AtomicU32::new(0),
3145 };
3146 let tuple_payload = PyType {
3147 base: unsafe {
3148 PyAtomicRef::from_optional_ref_without_retag(Some(clone_raw_ref(object_type_ptr)))
3149 },
3150 bases: PyRwLock::new(tuple_bases),
3151 mro: PyRwLock::new(vec![unsafe { clone_raw_ref(tuple_type_ptr) }, unsafe {
3152 clone_raw_ref(object_type_ptr)
3153 }]),
3154 subclasses: PyRwLock::default(),
3155 attributes: Default::default(),
3156 slots: tuple::PyTuple::make_slots(),
3157 heaptype_ext: None,
3158 tp_version_tag: core::sync::atomic::AtomicU32::new(0),
3159 };
3160
3161 let object_element =
3162 || -> PyObjectRef { unsafe { clone_raw_ref::<PyType>(object_type_ptr) }.into() };
3163 unsafe {
3164 init_inner(type_type_ptr, clone_raw_ref(type_type_ptr), type_payload);
3165 init_inner(
3166 object_type_ptr,
3167 clone_raw_ref(type_type_ptr),
3168 object_payload,
3169 );
3170 init_inner(tuple_type_ptr, clone_raw_ref(type_type_ptr), tuple_payload);
3171 init_inner(
3172 empty_tuple_ptr,
3173 clone_raw_ref(tuple_type_ptr),
3174 PyTuple::new_unchecked(Vec::new().into_boxed_slice()),
3175 );
3176 init_inner(
3177 type_bases_ptr,
3178 clone_raw_ref(tuple_type_ptr),
3179 PyTuple::new_unchecked(vec![object_element()].into_boxed_slice()),
3180 );
3181 init_inner(
3182 tuple_bases_ptr,
3183 clone_raw_ref(tuple_type_ptr),
3184 PyTuple::new_unchecked(vec![object_element()].into_boxed_slice()),
3185 );
3186 }
3187
3188 PyType::finalize_bootstrap_static(&tuple_type);
3189
3190 let weakref_bases =
3191 PyTuple::new_ref_typed_with_type(vec![object_type.clone()], tuple_type.clone());
3192 unsafe {
3193 crate::gc_state::gc_state()
3194 .untrack_object(NonNull::from(weakref_bases.as_untyped().as_object()));
3195 }
3196 weakref_bases.as_untyped().as_object().clear_gc_tracked();
3197 let weakref_payload = PyType {
3198 base: Some(object_type.clone()).into(),
3199 bases: PyRwLock::new(weakref_bases),
3200 mro: PyRwLock::new(vec![object_type.clone()]),
3201 subclasses: PyRwLock::default(),
3202 attributes: Default::default(),
3203 slots: PyWeak::make_slots(),
3204 heaptype_ext: None,
3205 tp_version_tag: core::sync::atomic::AtomicU32::new(0),
3206 };
3207 let weakref_type = PyRef::new_ref(weakref_payload, type_type.clone(), None);
3208 unsafe {
3210 crate::gc_state::gc_state()
3211 .untrack_object(core::ptr::NonNull::from(weakref_type.as_object()));
3212 }
3213 weakref_type.as_object().clear_gc_tracked();
3214 weakref_type.mro.write().insert(0, weakref_type.clone());
3216
3217 object_type.subclasses.write().push(
3218 type_type
3219 .as_object()
3220 .downgrade_with_weakref_typ_opt(None, weakref_type.clone())
3221 .unwrap(),
3222 );
3223
3224 object_type.subclasses.write().push(
3225 tuple_type
3226 .as_object()
3227 .downgrade_with_weakref_typ_opt(None, weakref_type.clone())
3228 .unwrap(),
3229 );
3230
3231 object_type.subclasses.write().push(
3232 weakref_type
3233 .as_object()
3234 .downgrade_with_weakref_typ_opt(None, weakref_type.clone())
3235 .unwrap(),
3236 );
3237
3238 BootstrapTypeHierarchy {
3239 type_type,
3240 object_type,
3241 tuple_type,
3242 weakref_type,
3243 empty_tuple,
3244 }
3245}
3246
3247#[cfg(test)]
3248mod tests {
3249 use super::*;
3250
3251 #[test]
3252 fn native_type_basicsize_includes_payload_padding() {
3253 use crate::class::PyClassDef;
3254
3255 #[pyclass(module = false, name = "PaddedPayload")]
3256 #[derive(Debug, PyPayload)]
3257 #[repr(align(64))]
3258 struct PaddedPayload;
3259
3260 #[pyclass]
3261 impl PaddedPayload {}
3262
3263 assert_eq!(
3264 PaddedPayload::BASICSIZE,
3265 core::mem::size_of::<Py<PaddedPayload>>()
3266 );
3267 }
3268
3269 #[test]
3270 fn native_subclass_inherits_getter_with_mixed_field_sizes() {
3271 use crate::class::PyClassImpl;
3272
3273 #[pyclass(module = false, name = "LayoutBase")]
3274 #[derive(Debug, PyPayload)]
3275 #[repr(align(8))]
3277 struct LayoutBase {
3278 value: PyObjectRef,
3279 }
3280
3281 #[pyclass(flags(BASETYPE))]
3282 impl Py<LayoutBase> {
3283 #[pygetset]
3284 fn value(&self) -> PyObjectRef {
3285 self.value.clone()
3286 }
3287 }
3288
3289 #[pyclass(module = false, name = "LayoutDerived", base = LayoutBase)]
3290 #[derive(Debug)]
3291 struct LayoutDerived {
3292 base: LayoutBase,
3293 extra: Option<u64>,
3294 }
3295
3296 #[pyclass]
3297 impl LayoutDerived {
3298 #[pygetset]
3299 fn extra(zelf: &Py<Self>) -> Option<u64> {
3300 zelf.extra
3301 }
3302 }
3303
3304 assert_eq!(core::mem::offset_of!(LayoutDerived, base), 0);
3305 crate::Interpreter::without_stdlib(Default::default()).enter(|vm| {
3306 let _ = LayoutBase::make_static_type();
3307 let _ = LayoutDerived::make_static_type();
3308 let value: PyObjectRef = vm.ctx.new_int(42).into();
3309 let obj = vm.new_pyobj(LayoutDerived {
3310 base: LayoutBase {
3311 value: value.clone(),
3312 },
3313 extra: Some(99),
3314 });
3315 assert!(obj.get_attr("value", vm).unwrap().is(&value));
3316 assert_eq!(
3317 obj.get_attr("extra", vm)
3318 .unwrap()
3319 .try_to_value::<u64>(vm)
3320 .unwrap(),
3321 99
3322 );
3323 });
3324 }
3325
3326 #[test]
3327 fn clear_reuses_storage_and_preserves_existing_edges() {
3328 use crate::builtins::PyList;
3329
3330 crate::Interpreter::without_stdlib(Default::default()).enter(|vm| {
3331 for tuple in [false, true] {
3332 for existing in [false, true] {
3333 let elements = vec![vm.ctx.none(), vm.ctx.none()];
3334 let allocation = elements.as_ptr();
3335 let mut sequence: Box<dyn Traverse> = if tuple {
3336 Box::new(PyTuple::new_unchecked(elements.into_boxed_slice()))
3337 } else {
3338 Box::new(PyList::from(elements))
3339 };
3340 let mut out = if existing {
3341 vec![vm.ctx.new_int(1).into()]
3342 } else {
3343 Vec::new()
3344 };
3345 sequence.clear(&mut out);
3346 if existing {
3347 assert_eq!(out[0].try_to_value::<i32>(vm).unwrap(), 1);
3348 } else {
3349 assert_eq!(out.as_ptr(), allocation);
3350 }
3351 assert_eq!(out.len(), 2 + usize::from(existing));
3352 assert!(out[usize::from(existing)..].iter().all(|x| vm.is_none(x)));
3353 sequence.traverse(&mut |_| panic!("cleared sequence still owns an edge"));
3354 }
3355 }
3356 });
3357 }
3358
3359 #[test]
3360 fn miri_test_type_initialization() {
3361 let hierarchy = init_type_hierarchy();
3362
3363 assert!(hierarchy.type_type.class().is(&hierarchy.type_type));
3364 assert!(hierarchy.object_type.class().is(&hierarchy.type_type));
3365 assert!(hierarchy.tuple_type.class().is(&hierarchy.type_type));
3366 assert!(hierarchy.weakref_type.class().is(&hierarchy.type_type));
3367
3368 let object_bases = hierarchy.object_type.bases.read();
3369 assert!(object_bases.as_slice().is_empty());
3370 assert!(object_bases.as_untyped().is(&hierarchy.empty_tuple));
3371 assert!(object_bases.as_untyped().class().is(&hierarchy.tuple_type));
3372 drop(object_bases);
3373
3374 for typ in [
3375 &hierarchy.type_type,
3376 &hierarchy.tuple_type,
3377 &hierarchy.weakref_type,
3378 ] {
3379 let bases = typ.bases.read();
3380 assert_eq!(bases.as_slice().len(), 1);
3381 assert!(bases.as_slice()[0].is(&hierarchy.object_type));
3382 assert!(bases.as_untyped().class().is(&hierarchy.tuple_type));
3383 }
3384 }
3385
3386 #[test]
3387 fn miri_test_drop() {
3388 let ctx = crate::Context::genesis();
3390 let obj = ctx.new_bytes(b"dfghjkl".to_vec());
3391 drop(obj);
3392 }
3393
3394 #[cfg(feature = "threading")]
3399 #[test]
3400 fn weakref_proxies_keep_their_class_while_linked() {
3401 const THREADS: usize = 8;
3402 const ROUNDS: usize = 20_000;
3403
3404 crate::Interpreter::without_stdlib(Default::default()).enter(|vm| {
3405 let target: PyObjectRef = vm
3406 .ctx
3407 .new_class(
3408 None,
3409 "WeakrefTarget",
3410 vm.ctx.types.object_type.to_owned(),
3411 Default::default(),
3412 )
3413 .into();
3414 let workers = (0..THREADS)
3415 .map(|_| {
3416 let thread_vm = vm.new_thread();
3417 let target = target.clone();
3418 std::thread::spawn(move || {
3419 thread_vm.run(|vm| {
3420 let proxy_type = vm.ctx.types.weakproxy_type.to_owned();
3421 for _ in 0..ROUNDS {
3422 let proxy = target
3423 .downgrade_with_typ(None, proxy_type.clone(), vm)
3424 .expect("a type object takes weakrefs");
3425 drop(proxy);
3426 vm.check_signals().unwrap();
3427 }
3428 })
3429 })
3430 })
3431 .collect::<Vec<_>>();
3432 vm.allow_threads(|| {
3436 for worker in workers {
3437 worker.join().unwrap();
3438 }
3439 });
3440 });
3441 }
3442}