Skip to content

Commit 483e4a2

Browse files
authored
Align psuedo ops to CPython 3.14.2 (RustPython#6846)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Optimized attribute and super-attribute bytecode emission and consolidated pseudo-instruction handling, improving consistency and stack-effect accuracy. * **Chores** * Adjusted opcode constants and simplified opcode/name mappings. * **New Features** * Added a utility to enumerate special method names and a helper for resolving dependency parent modules. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
2 parents 691d281 + c0bdb9a commit 483e4a2

5 files changed

Lines changed: 92 additions & 108 deletions

File tree

Lib/_opcode_metadata.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -245,10 +245,6 @@
245245
'SETUP_FINALLY': 264,
246246
'SETUP_WITH': 265,
247247
'STORE_FAST_MAYBE_NULL': 266,
248-
'LOAD_ATTR_METHOD': 267,
249-
'LOAD_SUPER_METHOD': 268,
250-
'LOAD_ZERO_SUPER_ATTR': 269,
251-
'LOAD_ZERO_SUPER_METHOD': 270,
252248
}
253249

254250
# CPython 3.13 compatible: opcodes < 44 have no argument

crates/codegen/src/compile.rs

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use rustpython_compiler_core::{
3131
self, AnyInstruction, Arg as OpArgMarker, BinaryOperator, BuildSliceArgCount, CodeObject,
3232
ComparisonOperator, ConstantData, ConvertValueOparg, Instruction, IntrinsicFunction1,
3333
Invert, OpArg, OpArgType, PseudoInstruction, SpecialMethod, UnpackExArgs,
34+
encode_load_attr_arg, encode_load_super_attr_arg,
3435
},
3536
};
3637
use rustpython_wtf8::Wtf8Buf;
@@ -2108,7 +2109,7 @@ impl Compiler {
21082109
if let Some(alias) = &name.asname {
21092110
for part in name.name.split('.').skip(1) {
21102111
let idx = self.name(part);
2111-
emit!(self, Instruction::LoadAttr { idx });
2112+
self.emit_load_attr(idx);
21122113
}
21132114
self.store_name(alias.as_str())?
21142115
} else {
@@ -6280,7 +6281,7 @@ impl Compiler {
62806281
self.compile_expression(value)?;
62816282
emit!(self, Instruction::Copy { index: 1_u32 });
62826283
let idx = self.name(attr);
6283-
emit!(self, Instruction::LoadAttr { idx });
6284+
self.emit_load_attr(idx);
62846285
AugAssignKind::Attr { idx }
62856286
}
62866287
_ => {
@@ -6615,19 +6616,17 @@ impl Compiler {
66156616
let idx = self.name(attr.as_str());
66166617
match super_type {
66176618
SuperCallType::TwoArg { .. } => {
6618-
// LoadSuperAttr (pseudo) - will be converted to real LoadSuperAttr
6619-
// with flags=0b10 (has_class=true, load_method=false) in ir.rs
6620-
emit!(self, Instruction::LoadSuperAttr { arg: idx });
6619+
self.emit_load_super_attr(idx);
66216620
}
66226621
SuperCallType::ZeroArg => {
6623-
emit!(self, PseudoInstruction::LoadZeroSuperAttr { idx });
6622+
self.emit_load_zero_super_attr(idx);
66246623
}
66256624
}
66266625
} else {
66276626
// Normal attribute access
66286627
self.compile_expression(value)?;
66296628
let idx = self.name(attr.as_str());
6630-
emit!(self, Instruction::LoadAttr { idx });
6629+
self.emit_load_attr(idx);
66316630
}
66326631
}
66336632
ast::Expr::Compare(ast::ExprCompare {
@@ -7070,19 +7069,19 @@ impl Compiler {
70707069
let idx = self.name(attr.as_str());
70717070
match super_type {
70727071
SuperCallType::TwoArg { .. } => {
7073-
emit!(self, PseudoInstruction::LoadSuperMethod { idx });
7072+
self.emit_load_super_method(idx);
70747073
}
70757074
SuperCallType::ZeroArg => {
7076-
emit!(self, PseudoInstruction::LoadZeroSuperMethod { idx });
7075+
self.emit_load_zero_super_method(idx);
70777076
}
70787077
}
70797078
self.codegen_call_helper(0, args)?;
70807079
} else {
7081-
// Normal method call: compile object, then LOAD_ATTR_METHOD
7082-
// LOAD_ATTR_METHOD pushes [method, self_or_null] on stack
7080+
// Normal method call: compile object, then LOAD_ATTR with method flag
7081+
// LOAD_ATTR(method=1) pushes [method, self_or_null] on stack
70837082
self.compile_expression(value)?;
70847083
let idx = self.name(attr.as_str());
7085-
emit!(self, PseudoInstruction::LoadAttrMethod { idx });
7084+
self.emit_load_attr_method(idx);
70867085
self.codegen_call_helper(0, args)?;
70877086
}
70887087
} else {
@@ -7782,6 +7781,48 @@ impl Compiler {
77827781
emit!(self, Instruction::ReturnValue)
77837782
}
77847783

7784+
/// Emit LOAD_ATTR for attribute access (method=false).
7785+
/// Encodes: (name_idx << 1) | 0
7786+
fn emit_load_attr(&mut self, name_idx: u32) {
7787+
let encoded = encode_load_attr_arg(name_idx, false);
7788+
self.emit_arg(encoded, |arg| Instruction::LoadAttr { idx: arg })
7789+
}
7790+
7791+
/// Emit LOAD_ATTR with method flag set (for method calls).
7792+
/// Encodes: (name_idx << 1) | 1
7793+
fn emit_load_attr_method(&mut self, name_idx: u32) {
7794+
let encoded = encode_load_attr_arg(name_idx, true);
7795+
self.emit_arg(encoded, |arg| Instruction::LoadAttr { idx: arg })
7796+
}
7797+
7798+
/// Emit LOAD_SUPER_ATTR for 2-arg super().attr access.
7799+
/// Encodes: (name_idx << 2) | 0b10 (method=0, class=1)
7800+
fn emit_load_super_attr(&mut self, name_idx: u32) {
7801+
let encoded = encode_load_super_attr_arg(name_idx, false, true);
7802+
self.emit_arg(encoded, |arg| Instruction::LoadSuperAttr { arg })
7803+
}
7804+
7805+
/// Emit LOAD_SUPER_ATTR for 2-arg super().method() call.
7806+
/// Encodes: (name_idx << 2) | 0b11 (method=1, class=1)
7807+
fn emit_load_super_method(&mut self, name_idx: u32) {
7808+
let encoded = encode_load_super_attr_arg(name_idx, true, true);
7809+
self.emit_arg(encoded, |arg| Instruction::LoadSuperAttr { arg })
7810+
}
7811+
7812+
/// Emit LOAD_SUPER_ATTR for 0-arg super().attr access.
7813+
/// Encodes: (name_idx << 2) | 0b00 (method=0, class=0)
7814+
fn emit_load_zero_super_attr(&mut self, name_idx: u32) {
7815+
let encoded = encode_load_super_attr_arg(name_idx, false, false);
7816+
self.emit_arg(encoded, |arg| Instruction::LoadSuperAttr { arg })
7817+
}
7818+
7819+
/// Emit LOAD_SUPER_ATTR for 0-arg super().method() call.
7820+
/// Encodes: (name_idx << 2) | 0b01 (method=1, class=0)
7821+
fn emit_load_zero_super_method(&mut self, name_idx: u32) {
7822+
let encoded = encode_load_super_attr_arg(name_idx, true, false);
7823+
self.emit_arg(encoded, |arg| Instruction::LoadSuperAttr { arg })
7824+
}
7825+
77857826
fn emit_return_value(&mut self) {
77867827
emit!(self, Instruction::ReturnValue)
77877828
}

crates/codegen/src/ir.rs

Lines changed: 4 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ use rustpython_compiler_core::{
77
bytecode::{
88
AnyInstruction, Arg, CodeFlags, CodeObject, CodeUnit, CodeUnits, ConstantData,
99
ExceptionTableEntry, InstrDisplayContext, Instruction, InstructionMetadata, Label, OpArg,
10-
PseudoInstruction, PyCodeLocationInfoKind, encode_exception_table, encode_load_attr_arg,
11-
encode_load_super_attr_arg,
10+
PseudoInstruction, PyCodeLocationInfoKind, encode_exception_table,
1211
},
1312
varint::{write_signed_varint, write_varint},
1413
};
@@ -207,62 +206,16 @@ impl CodeInfo {
207206
.filter(|b| b.next != BlockIdx::NULL || !b.instructions.is_empty())
208207
{
209208
for info in &mut block.instructions {
210-
// Special case for:
211-
// - `Instruction::LoadAttr`
212-
// - `Instruction::LoadSuperAttr`
213-
214-
if let Some(instr) = info.instr.real() {
215-
match instr {
216-
// LOAD_ATTR → encode with method flag=0
217-
Instruction::LoadAttr { idx } => {
218-
let encoded = encode_load_attr_arg(idx.get(info.arg), false);
219-
info.arg = OpArg(encoded);
220-
info.instr = Instruction::LoadAttr { idx: Arg::marker() }.into();
221-
}
222-
// LOAD_SUPER_ATTR → encode with flags=0b10 (method=0, class=1)
223-
Instruction::LoadSuperAttr { arg: idx } => {
224-
let encoded =
225-
encode_load_super_attr_arg(idx.get(info.arg), false, true);
226-
info.arg = OpArg(encoded);
227-
info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() }.into();
228-
}
229-
_ => {}
230-
}
231-
209+
// Real instructions are already encoded by compile.rs
210+
let Some(instr) = info.instr.pseudo() else {
232211
continue;
233-
}
234-
235-
let instr = info.instr.expect_pseudo();
212+
};
236213

237214
match instr {
238-
// LOAD_ATTR_METHOD pseudo → LOAD_ATTR (with method flag=1)
239-
PseudoInstruction::LoadAttrMethod { idx } => {
240-
let encoded = encode_load_attr_arg(idx.get(info.arg), true);
241-
info.arg = OpArg(encoded);
242-
info.instr = Instruction::LoadAttr { idx: Arg::marker() }.into();
243-
}
244215
// POP_BLOCK pseudo → NOP
245216
PseudoInstruction::PopBlock => {
246217
info.instr = Instruction::Nop.into();
247218
}
248-
// LOAD_SUPER_METHOD pseudo → LOAD_SUPER_ATTR (flags=0b11: method=1, class=1)
249-
PseudoInstruction::LoadSuperMethod { idx } => {
250-
let encoded = encode_load_super_attr_arg(idx.get(info.arg), true, true);
251-
info.arg = OpArg(encoded);
252-
info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() }.into();
253-
}
254-
// LOAD_ZERO_SUPER_ATTR pseudo → LOAD_SUPER_ATTR (flags=0b00: method=0, class=0)
255-
PseudoInstruction::LoadZeroSuperAttr { idx } => {
256-
let encoded = encode_load_super_attr_arg(idx.get(info.arg), false, false);
257-
info.arg = OpArg(encoded);
258-
info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() }.into();
259-
}
260-
// LOAD_ZERO_SUPER_METHOD pseudo → LOAD_SUPER_ATTR (flags=0b01: method=1, class=0)
261-
PseudoInstruction::LoadZeroSuperMethod { idx } => {
262-
let encoded = encode_load_super_attr_arg(idx.get(info.arg), true, false);
263-
info.arg = OpArg(encoded);
264-
info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() }.into();
265-
}
266219
// LOAD_CLOSURE pseudo → LOAD_FAST (with varnames offset)
267220
PseudoInstruction::LoadClosure(idx) => {
268221
let varnames_len = varname_cache.len() as u32;

crates/compiler-core/src/bytecode/instruction.rs

Lines changed: 15 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,8 @@ impl InstructionMetadata for Instruction {
426426
| Self::JumpForward { target: l }
427427
| Self::PopJumpIfTrue { target: l }
428428
| Self::PopJumpIfFalse { target: l }
429+
| Self::PopJumpIfNone { target: l }
430+
| Self::PopJumpIfNotNone { target: l }
429431
| Self::ForIter { target: l }
430432
| Self::Send { target: l } => Some(*l),
431433
_ => None,
@@ -472,7 +474,13 @@ impl InstructionMetadata for Instruction {
472474
Self::LoadFromDictOrDeref(_) => 1,
473475
Self::StoreSubscr => -3,
474476
Self::DeleteSubscr => -2,
475-
Self::LoadAttr { .. } => 0,
477+
Self::LoadAttr { idx } => {
478+
// Stack effect depends on method flag in encoded oparg
479+
// method=false: pop obj, push attr → effect = 0
480+
// method=true: pop obj, push (method, self_or_null) → effect = +1
481+
let (_, is_method) = decode_load_attr_arg(idx.get(arg));
482+
if is_method { 1 } else { 0 }
483+
}
476484
Self::StoreAttr { .. } => -2,
477485
Self::DeleteAttr { .. } => -1,
478486
Self::LoadCommonConstant { .. } => 1,
@@ -921,47 +929,22 @@ impl InstructionMetadata for Instruction {
921929

922930
/// Instructions used by the compiler. They are not executed by the VM.
923931
///
924-
/// CPython 3.14.2 aligned (256-266), RustPython-specific variants start at 267.
932+
/// CPython 3.14.2 aligned (256-266).
925933
#[derive(Clone, Copy, Debug)]
926934
#[repr(u16)]
927935
pub enum PseudoInstruction {
928936
// CPython 3.14.2 pseudo instructions (256-266)
929937
AnnotationsPlaceholder = 256,
930-
Jump {
931-
target: Arg<Label>,
932-
} = 257,
933-
JumpIfFalse {
934-
target: Arg<Label>,
935-
} = 258,
936-
JumpIfTrue {
937-
target: Arg<Label>,
938-
} = 259,
939-
JumpNoInterrupt {
940-
target: Arg<Label>,
941-
} = 260,
938+
Jump { target: Arg<Label> } = 257,
939+
JumpIfFalse { target: Arg<Label> } = 258,
940+
JumpIfTrue { target: Arg<Label> } = 259,
941+
JumpNoInterrupt { target: Arg<Label> } = 260,
942942
LoadClosure(Arg<NameIdx>) = 261,
943943
PopBlock = 262,
944944
SetupCleanup = 263,
945945
SetupFinally = 264,
946946
SetupWith = 265,
947947
StoreFastMaybeNull(Arg<NameIdx>) = 266,
948-
949-
// RustPython-specific pseudo instructions (267+)
950-
LoadAttrMethod {
951-
idx: Arg<NameIdx>,
952-
} = 267,
953-
// "Zero" variants are for 0-arg super() calls (has_class=false).
954-
// Non-"Zero" variants are for 2-arg super(cls, self) calls (has_class=true).
955-
/// 2-arg super(cls, self).method() - has_class=true, load_method=true
956-
LoadSuperMethod {
957-
idx: Arg<NameIdx>,
958-
} = 268,
959-
LoadZeroSuperAttr {
960-
idx: Arg<NameIdx>,
961-
} = 269,
962-
LoadZeroSuperMethod {
963-
idx: Arg<NameIdx>,
964-
} = 270,
965948
}
966949

967950
const _: () = assert!(mem::size_of::<PseudoInstruction>() == 2);
@@ -980,7 +963,7 @@ impl TryFrom<u16> for PseudoInstruction {
980963
#[inline]
981964
fn try_from(value: u16) -> Result<Self, MarshalError> {
982965
let start = u16::from(Self::AnnotationsPlaceholder);
983-
let end = u16::from(Self::LoadZeroSuperMethod { idx: Arg::marker() });
966+
let end = u16::from(Self::StoreFastMaybeNull(Arg::marker()));
984967

985968
if (start..=end).contains(&value) {
986969
Ok(unsafe { mem::transmute::<u16, Self>(value) })
@@ -1022,10 +1005,6 @@ impl InstructionMetadata for PseudoInstruction {
10221005
Self::SetupFinally => 0,
10231006
Self::SetupWith => 0,
10241007
Self::StoreFastMaybeNull(_) => -1,
1025-
Self::LoadAttrMethod { .. } => 1, // pop obj, push method + self_or_null
1026-
Self::LoadSuperMethod { .. } => -3 + 2, // pop 3, push [method, self_or_null]
1027-
Self::LoadZeroSuperAttr { .. } => -3 + 1, // pop 3, push [attr]
1028-
Self::LoadZeroSuperMethod { .. } => -3 + 2, // pop 3, push [method, self_or_null]
10291008
}
10301009
}
10311010

crates/stdlib/src/_opcode.rs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ mod _opcode {
3434
}
3535

3636
impl Opcode {
37-
// https://github.com/python/cpython/blob/bcee1c322115c581da27600f2ae55e5439c027eb/Include/opcode_ids.h#L238
38-
const HAVE_ARGUMENT: i32 = 44;
37+
// https://github.com/python/cpython/blob/v3.14.2/Include/opcode_ids.h#L252
38+
const HAVE_ARGUMENT: i32 = 43;
3939

4040
pub fn try_from_pyint(raw: PyIntRef, vm: &VirtualMachine) -> PyResult<Self> {
4141
let instruction = raw
@@ -92,7 +92,7 @@ mod _opcode {
9292
| Instruction::StoreAttr { .. }
9393
| Instruction::StoreGlobal(_)
9494
| Instruction::StoreName(_)
95-
) | AnyInstruction::Pseudo(PseudoInstruction::LoadAttrMethod { .. }))
95+
))
9696
)
9797
}
9898

@@ -148,8 +148,11 @@ mod _opcode {
148148
}
149149
}
150150

151+
// prepare specialization
151152
#[pyattr]
152153
const ENABLE_SPECIALIZATION: i8 = 1;
154+
#[allow(dead_code)]
155+
const ENABLE_SPECIALIZATION_FT: i8 = 1;
153156

154157
#[derive(FromArgs)]
155158
struct StackEffectArgs {
@@ -303,6 +306,7 @@ mod _opcode {
303306
("NB_INPLACE_SUBTRACT", "-="),
304307
("NB_INPLACE_TRUE_DIVIDE", "/="),
305308
("NB_INPLACE_XOR", "^="),
309+
("NB_SUBSCR", "[]"),
306310
]
307311
.into_iter()
308312
.map(|(a, b)| {
@@ -314,8 +318,19 @@ mod _opcode {
314318
}
315319

316320
#[pyfunction]
317-
fn get_executor(_code: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
318-
// TODO
321+
fn get_special_method_names(vm: &VirtualMachine) -> Vec<PyObjectRef> {
322+
["__enter__", "__exit__", "__aenter__", "__aexit__"]
323+
.into_iter()
324+
.map(|x| vm.ctx.new_str(x).into())
325+
.collect()
326+
}
327+
328+
#[pyfunction]
329+
fn get_executor(
330+
_code: PyObjectRef,
331+
_offset: i32,
332+
vm: &VirtualMachine,
333+
) -> PyResult<PyObjectRef> {
319334
Ok(vm.ctx.none())
320335
}
321336

0 commit comments

Comments
 (0)