Skip to content

Commit 72e91ac

Browse files
committed
vm: align LOAD_ATTR property/getattribute specializations with CPython
1 parent c46df23 commit 72e91ac

File tree

1 file changed

+74
-11
lines changed

1 file changed

+74
-11
lines changed

crates/vm/src/frame.rs

Lines changed: 74 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)