@@ -10,7 +10,7 @@ use crate::{
1010 PyStrInterned , PyTemplate , PyTraceback , PyType , PyUtf8Str ,
1111 asyncgenerator:: PyAsyncGenWrappedValue ,
1212 builtin_func:: PyNativeFunction ,
13- descriptor:: PyMethodDescriptor ,
13+ descriptor:: { MemberGetter , PyMemberDescriptor , PyMethodDescriptor } ,
1414 frame:: stack_analysis,
1515 function:: { PyCell , PyCellRef , PyFunction } ,
1616 list:: PyListIterator ,
@@ -3090,6 +3090,40 @@ impl ExecutingFrame<'_> {
30903090 }
30913091 self . load_attr_slow ( vm, oparg)
30923092 }
3093+ Instruction :: LoadAttrSlot => {
3094+ let oparg = LoadAttr :: new ( u32:: from ( arg) ) ;
3095+ let instr_idx = self . lasti ( ) as usize - 1 ;
3096+ let cache_base = instr_idx + 1 ;
3097+
3098+ let owner = self . top_value ( ) ;
3099+ let type_version = self . code . instructions . read_cache_u32 ( cache_base + 1 ) ;
3100+
3101+ if type_version != 0 && owner. class ( ) . tp_version_tag . load ( Acquire ) == type_version
3102+ {
3103+ let slot_offset =
3104+ self . code . instructions . read_cache_u32 ( cache_base + 3 ) as usize ;
3105+ if let Some ( value) = owner. get_slot ( slot_offset) {
3106+ self . pop_value ( ) ;
3107+ if oparg. is_method ( ) {
3108+ self . push_value ( value) ;
3109+ self . push_value_opt ( None ) ;
3110+ } else {
3111+ self . push_value ( value) ;
3112+ }
3113+ return Ok ( None ) ;
3114+ }
3115+ // Slot is None → AttributeError (fall through to slow path)
3116+ }
3117+ unsafe {
3118+ self . code
3119+ . instructions
3120+ . replace_op ( instr_idx, Instruction :: LoadAttr { idx : Arg :: marker ( ) } ) ;
3121+ self . code
3122+ . instructions
3123+ . write_adaptive_counter ( cache_base, ADAPTIVE_BACKOFF_VALUE ) ;
3124+ }
3125+ self . load_attr_slow ( vm, oparg)
3126+ }
30933127 Instruction :: StoreAttrInstanceValue => {
30943128 let attr_idx = u32:: from ( arg) ;
30953129 let instr_idx = self . lasti ( ) as usize - 1 ;
@@ -3118,6 +3152,35 @@ impl ExecutingFrame<'_> {
31183152 }
31193153 self . store_attr ( vm, attr_idx)
31203154 }
3155+ Instruction :: StoreAttrSlot => {
3156+ let instr_idx = self . lasti ( ) as usize - 1 ;
3157+ let cache_base = instr_idx + 1 ;
3158+ let type_version = self . code . instructions . read_cache_u32 ( cache_base + 1 ) ;
3159+ let version_match = type_version != 0 && {
3160+ let owner = self . top_value ( ) ;
3161+ owner. class ( ) . tp_version_tag . load ( Acquire ) == type_version
3162+ } ;
3163+
3164+ if version_match {
3165+ let slot_offset =
3166+ self . code . instructions . read_cache_u32 ( cache_base + 3 ) as usize ;
3167+ let owner = self . pop_value ( ) ;
3168+ let value = self . pop_value ( ) ;
3169+ owner. set_slot ( slot_offset, Some ( value) ) ;
3170+ return Ok ( None ) ;
3171+ }
3172+ // Deoptimize
3173+ let attr_idx = u32:: from ( arg) ;
3174+ unsafe {
3175+ self . code
3176+ . instructions
3177+ . replace_op ( instr_idx, Instruction :: StoreAttr { idx : Arg :: marker ( ) } ) ;
3178+ self . code
3179+ . instructions
3180+ . write_adaptive_counter ( cache_base, ADAPTIVE_BACKOFF_VALUE ) ;
3181+ }
3182+ self . store_attr ( vm, attr_idx)
3183+ }
31213184 Instruction :: StoreSubscrListInt => {
31223185 // Stack: [value, obj, idx] (TOS=idx, TOS1=obj, TOS2=value)
31233186 let idx = self . pop_value ( ) ;
@@ -5729,8 +5792,32 @@ impl ExecutingFrame<'_> {
57295792 descr. class ( ) . slots . descr_get . load ( ) . is_some ( )
57305793 } ) ;
57315794
5732- if has_data_descr || has_descr_get {
5733- // Data descriptor or non-data descriptor with __get__ — can't specialize
5795+ if has_data_descr {
5796+ // Check for member descriptor (slot access)
5797+ if let Some ( ref descr) = cls_attr
5798+ && let Some ( member_descr) = descr. downcast_ref :: < PyMemberDescriptor > ( )
5799+ && let MemberGetter :: Offset ( offset) = member_descr. member . getter
5800+ {
5801+ unsafe {
5802+ self . code
5803+ . instructions
5804+ . write_cache_u32 ( cache_base + 1 , type_version) ;
5805+ self . code
5806+ . instructions
5807+ . write_cache_u32 ( cache_base + 3 , offset as u32 ) ;
5808+ self . code
5809+ . instructions
5810+ . replace_op ( instr_idx, Instruction :: LoadAttrSlot ) ;
5811+ }
5812+ } else {
5813+ unsafe {
5814+ self . code
5815+ . instructions
5816+ . write_adaptive_counter ( cache_base, ADAPTIVE_BACKOFF_VALUE ) ;
5817+ }
5818+ }
5819+ } else if has_descr_get {
5820+ // Non-data descriptor with __get__ — can't specialize
57345821 unsafe {
57355822 self . code
57365823 . instructions
@@ -6486,14 +6573,40 @@ impl ExecutingFrame<'_> {
64866573 return ;
64876574 }
64886575
6489- // Check no data descriptor for this attr
6576+ // Check for data descriptor
64906577 let attr_name = self . code . names [ attr_idx as usize ] ;
6491- let has_data_descr = cls. get_attr ( attr_name) . is_some_and ( |descr| {
6578+ let cls_attr = cls. get_attr ( attr_name) ;
6579+ let has_data_descr = cls_attr. as_ref ( ) . is_some_and ( |descr| {
64926580 let descr_cls = descr. class ( ) ;
6493- descr_cls. slots . descr_get . load ( ) . is_some ( ) && descr_cls. slots . descr_set . load ( ) . is_some ( )
6581+ descr_cls. slots . descr_get . load ( ) . is_some ( )
6582+ && descr_cls. slots . descr_set . load ( ) . is_some ( )
64946583 } ) ;
64956584
6496- if !has_data_descr && owner. dict ( ) . is_some ( ) {
6585+ if has_data_descr {
6586+ // Check for member descriptor (slot access)
6587+ if let Some ( ref descr) = cls_attr
6588+ && let Some ( member_descr) = descr. downcast_ref :: < PyMemberDescriptor > ( )
6589+ && let MemberGetter :: Offset ( offset) = member_descr. member . getter
6590+ {
6591+ unsafe {
6592+ self . code
6593+ . instructions
6594+ . write_cache_u32 ( cache_base + 1 , type_version) ;
6595+ self . code
6596+ . instructions
6597+ . write_cache_u32 ( cache_base + 3 , offset as u32 ) ;
6598+ self . code
6599+ . instructions
6600+ . replace_op ( instr_idx, Instruction :: StoreAttrSlot ) ;
6601+ }
6602+ } else {
6603+ unsafe {
6604+ self . code
6605+ . instructions
6606+ . write_adaptive_counter ( cache_base, ADAPTIVE_BACKOFF_VALUE ) ;
6607+ }
6608+ }
6609+ } else if owner. dict ( ) . is_some ( ) {
64976610 unsafe {
64986611 self . code
64996612 . instructions
0 commit comments