@@ -17,7 +17,7 @@ use super::{
1717} ;
1818use crate :: object:: traverse_object:: PyObjVTable ;
1919use crate :: {
20- builtins:: { PyDictRef , PyType , PyTypeRef } ,
20+ builtins:: { PyDict , PyDictRef , PyType , PyTypeRef } ,
2121 common:: {
2222 atomic:: { Ordering , PyAtomic , Radium } ,
2323 linked_list:: { Link , Pointers } ,
@@ -257,8 +257,6 @@ bitflags::bitflags! {
257257 const SHARED_INLINE = 1 << 5 ;
258258 /// Use deferred reference counting
259259 const DEFERRED = 1 << 6 ;
260- /// Object has ObjExt prefix allocation
261- const HAS_EXT = 1 << 7 ;
262260 }
263261}
264262
@@ -356,11 +354,20 @@ pub(super) struct PyInner<T> {
356354pub ( crate ) const SIZEOF_PYOBJECT_HEAD : usize = core:: mem:: size_of :: < PyInner < ( ) > > ( ) ;
357355
358356impl < T > PyInner < T > {
359- /// Check if this object has an ObjExt prefix.
360- /// Uses the per-instance HAS_EXT bit in gc_bits, set at allocation time.
357+ /// Check if this object has an ObjExt prefix based on type flags.
358+ /// Uses raw pointer reads to avoid Stacked Borrows violations during bootstrap,
359+ /// where type objects have self-referential typ pointers that may be mutated.
361360 #[ inline( always) ]
362361 fn has_ext ( & self ) -> bool {
363- GcBits :: from_bits_retain ( self . gc_bits . load ( Ordering :: Relaxed ) ) . contains ( GcBits :: HAS_EXT )
362+ // Read slots via raw pointers only — creating a &Py<PyType> reference
363+ // would retag the entire object, conflicting with &mut writes during bootstrap.
364+ let typ_ptr = self . typ . load_raw ( ) ;
365+ let slots = unsafe { core:: ptr:: addr_of!( ( * typ_ptr) . 0 . payload. slots) } ;
366+ let flags = unsafe { core:: ptr:: addr_of!( ( * slots) . flags) . read ( ) } ;
367+ let member_count = unsafe { core:: ptr:: addr_of!( ( * slots) . member_count) . read ( ) } ;
368+ flags. has_feature ( crate :: types:: PyTypeFlags :: HAS_DICT )
369+ || flags. has_feature ( crate :: types:: PyTypeFlags :: HAS_WEAKREF )
370+ || member_count > 0
364371 }
365372
366373 /// Access the ObjExt prefix at a negative offset from this PyInner.
@@ -943,16 +950,20 @@ impl<T: PyPayload + core::fmt::Debug> PyInner<T> {
943950 /// For objects with ext, the allocation layout is: [ObjExt][PyInner<T>]
944951 fn new ( payload : T , typ : PyTypeRef , dict : Option < PyDictRef > ) -> * mut Self {
945952 let member_count = typ. slots . member_count ;
946- let needs_ext = dict. is_some ( )
947- || typ
948- . slots
949- . flags
950- . has_feature ( crate :: types:: PyTypeFlags :: HAS_DICT )
953+ let needs_ext = typ
954+ . slots
955+ . flags
956+ . has_feature ( crate :: types:: PyTypeFlags :: HAS_DICT )
951957 || typ
952958 . slots
953959 . flags
954960 . has_feature ( crate :: types:: PyTypeFlags :: HAS_WEAKREF )
955961 || member_count > 0 ;
962+ debug_assert ! (
963+ needs_ext || dict. is_none( ) ,
964+ "dict passed to type '{}' without HAS_DICT flag" ,
965+ typ. name( )
966+ ) ;
956967
957968 if needs_ext {
958969 let ext_layout = core:: alloc:: Layout :: new :: < ObjExt > ( ) ;
@@ -975,7 +986,7 @@ impl<T: PyPayload + core::fmt::Debug> PyInner<T> {
975986 inner_ptr. write ( Self {
976987 ref_count : RefCount :: new ( ) ,
977988 vtable : PyObjVTable :: of :: < T > ( ) ,
978- gc_bits : Radium :: new ( GcBits :: HAS_EXT . bits ( ) ) ,
989+ gc_bits : Radium :: new ( 0 ) ,
979990 gc_generation : Radium :: new ( GC_UNTRACKED ) ,
980991 gc_pointers : Pointers :: new ( ) ,
981992 typ : PyAtomicRef :: from ( typ) ,
@@ -1660,6 +1671,8 @@ impl PyObject {
16601671 if let Some ( ext) = obj. 0 . ext_ref ( ) {
16611672 if let Some ( dict) = ext. dict . as_ref ( ) {
16621673 let dict_ref = dict. get ( ) ;
1674+ // Clear dict entries to break cycles, then collect the dict itself
1675+ PyDict :: clear ( & dict_ref) ;
16631676 result. push ( dict_ref. into ( ) ) ;
16641677 }
16651678 for slot in ext. slots . iter ( ) {
@@ -2330,7 +2343,7 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) {
23302343 PyInner :: <PyType > {
23312344 ref_count: RefCount :: new( ) ,
23322345 vtable: PyObjVTable :: of:: <PyType >( ) ,
2333- gc_bits: Radium :: new( GcBits :: HAS_EXT . bits ( ) ) ,
2346+ gc_bits: Radium :: new( 0 ) ,
23342347 gc_generation: Radium :: new( GC_UNTRACKED ) ,
23352348 gc_pointers: Pointers :: new( ) ,
23362349 payload: type_payload,
@@ -2345,7 +2358,7 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) {
23452358 PyInner :: <PyType > {
23462359 ref_count: RefCount :: new( ) ,
23472360 vtable: PyObjVTable :: of:: <PyType >( ) ,
2348- gc_bits: Radium :: new( GcBits :: HAS_EXT . bits ( ) ) ,
2361+ gc_bits: Radium :: new( 0 ) ,
23492362 gc_generation: Radium :: new( GC_UNTRACKED ) ,
23502363 gc_pointers: Pointers :: new( ) ,
23512364 payload: object_payload,
0 commit comments