Skip to content

Commit 57cd6b2

Browse files
committed
Add LoadAttrSlot, StoreAttrSlot specialization for __slots__ access
- LoadAttrSlot: direct obj.get_slot(offset) bypassing descriptor protocol - StoreAttrSlot: direct obj.set_slot(offset, value) bypassing descriptor protocol - Detect PyMemberDescriptor with MemberGetter::Offset in specialize_load_attr/store_attr - Cache slot offset in cache_base+3
1 parent 56b8ae8 commit 57cd6b2

File tree

1 file changed

+120
-7
lines changed

1 file changed

+120
-7
lines changed

crates/vm/src/frame.rs

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

Comments
 (0)