@@ -3710,9 +3710,37 @@ impl ExecutingFrame<'_> {
37103710 }
37113711 Instruction :: LoadAttrGetattributeOverridden => {
37123712 let oparg = LoadAttr :: new ( u32:: from ( arg) ) ;
3713- self . deoptimize ( Instruction :: LoadAttr {
3714- namei : Arg :: marker ( ) ,
3715- } ) ;
3713+ let instr_idx = self . lasti ( ) as usize - 1 ;
3714+ let cache_base = instr_idx + 1 ;
3715+ let owner = self . top_value ( ) ;
3716+ let type_version = self . code . instructions . read_cache_u32 ( cache_base + 1 ) ;
3717+ let func_version = self . code . instructions . read_cache_u32 ( cache_base + 3 ) ;
3718+
3719+ if !oparg. is_method ( )
3720+ && !self . specialization_eval_frame_active ( vm)
3721+ && !vm. reached_c_stack_limit ( )
3722+ && type_version != 0
3723+ && func_version != 0
3724+ && owner. class ( ) . tp_version_tag . load ( Acquire ) == type_version
3725+ && let Some ( func_obj) =
3726+ self . try_read_cached_descriptor ( cache_base, type_version)
3727+ && let Some ( func) = func_obj. downcast_ref_if_exact :: < PyFunction > ( vm)
3728+ && func. func_version ( ) == func_version
3729+ && func. can_specialize_call ( 2 )
3730+ {
3731+ let owner = self . pop_value ( ) ;
3732+ let attr_name = self . code . names [ oparg. name_idx ( ) as usize ] . to_owned ( ) . into ( ) ;
3733+ let result = func. invoke_exact_args ( vec ! [ owner, attr_name] , vm) ?;
3734+ self . push_value ( result) ;
3735+ return Ok ( None ) ;
3736+ }
3737+ self . deoptimize_at (
3738+ Instruction :: LoadAttr {
3739+ namei : Arg :: marker ( ) ,
3740+ } ,
3741+ instr_idx,
3742+ cache_base,
3743+ ) ;
37163744 self . load_attr_slow ( vm, oparg)
37173745 }
37183746 Instruction :: LoadAttrSlot => {
@@ -3763,13 +3791,16 @@ impl ExecutingFrame<'_> {
37633791 let type_version = self . code . instructions . read_cache_u32 ( cache_base + 1 ) ;
37643792
37653793 if type_version != 0
3794+ && !self . specialization_eval_frame_active ( vm)
3795+ && !vm. reached_c_stack_limit ( )
37663796 && owner. class ( ) . tp_version_tag . load ( Acquire ) == type_version
3767- && let Some ( descr) = self . try_read_cached_descriptor ( cache_base, type_version)
3768- && let Some ( prop) = descr. downcast_ref :: < PyProperty > ( )
3769- && let Some ( getter) = prop. get_fget ( )
3797+ && let Some ( fget_obj) =
3798+ self . try_read_cached_descriptor ( cache_base, type_version)
3799+ && let Some ( func) = fget_obj. downcast_ref_if_exact :: < PyFunction > ( vm)
3800+ && func. can_specialize_call ( 1 )
37703801 {
37713802 let owner = self . pop_value ( ) ;
3772- let result = getter . call ( ( owner, ) , vm) ?;
3803+ let result = func . invoke_exact_args ( vec ! [ owner] , vm) ?;
37733804 self . push_value ( result) ;
37743805 return Ok ( None ) ;
37753806 }
@@ -7148,6 +7179,34 @@ impl ExecutingFrame<'_> {
71487179 . load ( )
71497180 . is_some_and ( |f| f as usize == PyBaseObject :: getattro as * const ( ) as usize ) ;
71507181 if !is_default_getattro {
7182+ let mut type_version = cls. tp_version_tag . load ( Acquire ) ;
7183+ if type_version == 0 {
7184+ type_version = cls. assign_version_tag ( ) ;
7185+ }
7186+ if type_version != 0
7187+ && !oparg. is_method ( )
7188+ && !self . specialization_eval_frame_active ( _vm)
7189+ && let Some ( getattribute) = cls. get_attr ( identifier ! ( _vm, __getattribute__) )
7190+ && let Some ( func) = getattribute. downcast_ref_if_exact :: < PyFunction > ( _vm)
7191+ && func. can_specialize_call ( 2 )
7192+ {
7193+ let func_version = func. get_version_for_current_state ( ) ;
7194+ if func_version != 0 {
7195+ let func_ptr = & * getattribute as * const PyObject as usize ;
7196+ unsafe {
7197+ self . code
7198+ . instructions
7199+ . write_cache_u32 ( cache_base + 3 , func_version) ;
7200+ self . write_cached_descriptor ( cache_base, type_version, func_ptr) ;
7201+ }
7202+ self . specialize_at (
7203+ instr_idx,
7204+ cache_base,
7205+ Instruction :: LoadAttrGetattributeOverridden ,
7206+ ) ;
7207+ return ;
7208+ }
7209+ }
71517210 unsafe {
71527211 self . code . instructions . write_adaptive_counter (
71537212 cache_base,
@@ -7254,12 +7313,16 @@ impl ExecutingFrame<'_> {
72547313 }
72557314 self . specialize_at ( instr_idx, cache_base, Instruction :: LoadAttrSlot ) ;
72567315 } else if let Some ( ref descr) = cls_attr
7257- && descr. downcast_ref :: < PyProperty > ( ) . is_some ( )
7316+ && let Some ( prop) = descr. downcast_ref :: < PyProperty > ( )
7317+ && let Some ( fget) = prop. get_fget ( )
7318+ && let Some ( func) = fget. downcast_ref_if_exact :: < PyFunction > ( _vm)
7319+ && func. can_specialize_call ( 1 )
7320+ && !self . specialization_eval_frame_active ( _vm)
72587321 {
7259- // Property descriptor — cache the property object pointer
7260- let descr_ptr = & * * descr as * const PyObject as usize ;
7322+ // Property specialization caches fget directly, matching CPython.
7323+ let fget_ptr = & * fget as * const PyObject as usize ;
72617324 unsafe {
7262- self . write_cached_descriptor ( cache_base, type_version, descr_ptr ) ;
7325+ self . write_cached_descriptor ( cache_base, type_version, fget_ptr ) ;
72637326 }
72647327 self . specialize_at ( instr_idx, cache_base, Instruction :: LoadAttrProperty ) ;
72657328 } else {
0 commit comments