Skip to content

Commit 29bb8b4

Browse files
authored
Super instructions (#6694)
* super instructions * Fix classcell * ZeroArg
1 parent 12f08d6 commit 29bb8b4

File tree

7 files changed

+389
-36
lines changed

7 files changed

+389
-36
lines changed

Lib/_opcode_metadata.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@
138138
'JUMP_IF_NOT_EXC_MATCH': 131,
139139
'SET_EXC_INFO': 134,
140140
'SUBSCRIPT': 135,
141+
'LOAD_SUPER_METHOD': 136,
142+
'LOAD_ZERO_SUPER_ATTR': 137,
143+
'LOAD_ZERO_SUPER_METHOD': 138,
141144
'RESUME': 149,
142145
'JUMP': 252,
143146
'LOAD_CLOSURE': 253,

crates/codegen/src/compile.rs

Lines changed: 209 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,18 @@ pub enum FBlockDatum {
7575
ExceptionName(String),
7676
}
7777

78+
/// Type of super() call optimization detected by can_optimize_super_call()
79+
#[derive(Debug, Clone)]
80+
enum SuperCallType<'a> {
81+
/// super(class, self) - explicit 2-argument form
82+
TwoArg {
83+
class_arg: &'a Expr,
84+
self_arg: &'a Expr,
85+
},
86+
/// super() - implicit 0-argument form (uses __class__ cell)
87+
ZeroArg,
88+
}
89+
7890
#[derive(Debug, Clone)]
7991
pub struct FBlockInfo {
8092
pub fb_type: FBlockType,
@@ -661,6 +673,153 @@ impl Compiler {
661673
self.symbol_table_stack.pop().expect("compiler bug")
662674
}
663675

676+
/// Check if a super() call can be optimized
677+
/// Returns Some(SuperCallType) if optimization is possible, None otherwise
678+
fn can_optimize_super_call<'a>(
679+
&self,
680+
value: &'a Expr,
681+
attr: &str,
682+
) -> Option<SuperCallType<'a>> {
683+
use ruff_python_ast::*;
684+
685+
// 1. value must be a Call expression
686+
let Expr::Call(ExprCall {
687+
func, arguments, ..
688+
}) = value
689+
else {
690+
return None;
691+
};
692+
693+
// 2. func must be Name("super")
694+
let Expr::Name(ExprName { id, .. }) = func.as_ref() else {
695+
return None;
696+
};
697+
if id.as_str() != "super" {
698+
return None;
699+
}
700+
701+
// 3. attr must not be "__class__"
702+
if attr == "__class__" {
703+
return None;
704+
}
705+
706+
// 4. No keyword arguments
707+
if !arguments.keywords.is_empty() {
708+
return None;
709+
}
710+
711+
// 5. Must be inside a function (not at module level or class body)
712+
if !self.ctx.in_func() {
713+
return None;
714+
}
715+
716+
// 6. "super" must be GlobalImplicit (not redefined locally or at module level)
717+
let table = self.current_symbol_table();
718+
if let Some(symbol) = table.lookup("super")
719+
&& symbol.scope != SymbolScope::GlobalImplicit
720+
{
721+
return None;
722+
}
723+
// Also check top-level scope to detect module-level shadowing.
724+
// Only block if super is actually *bound* at module level (not just used).
725+
if let Some(top_table) = self.symbol_table_stack.first()
726+
&& let Some(sym) = top_table.lookup("super")
727+
&& sym.scope != SymbolScope::GlobalImplicit
728+
{
729+
return None;
730+
}
731+
732+
// 7. Check argument pattern
733+
let args = &arguments.args;
734+
735+
// No starred expressions allowed
736+
if args.iter().any(|arg| matches!(arg, Expr::Starred(_))) {
737+
return None;
738+
}
739+
740+
match args.len() {
741+
2 => {
742+
// 2-arg: super(class, self)
743+
Some(SuperCallType::TwoArg {
744+
class_arg: &args[0],
745+
self_arg: &args[1],
746+
})
747+
}
748+
0 => {
749+
// 0-arg: super() - need __class__ cell and first parameter
750+
// Enclosing function should have at least one positional argument
751+
let info = self.code_stack.last()?;
752+
if info.metadata.argcount == 0 && info.metadata.posonlyargcount == 0 {
753+
return None;
754+
}
755+
756+
// Check if __class__ is available as a cell/free variable
757+
// The scope must be Free (from enclosing class) or have FREE_CLASS flag
758+
if let Some(symbol) = table.lookup("__class__") {
759+
if symbol.scope != SymbolScope::Free
760+
&& !symbol.flags.contains(SymbolFlags::FREE_CLASS)
761+
{
762+
return None;
763+
}
764+
} else {
765+
// __class__ not in symbol table, optimization not possible
766+
return None;
767+
}
768+
769+
Some(SuperCallType::ZeroArg)
770+
}
771+
_ => None, // 1 or 3+ args - not optimizable
772+
}
773+
}
774+
775+
/// Load arguments for super() optimization onto the stack
776+
/// Stack result: [global_super, class, self]
777+
fn load_args_for_super(&mut self, super_type: &SuperCallType<'_>) -> CompileResult<()> {
778+
// 1. Load global super
779+
self.compile_name("super", NameUsage::Load)?;
780+
781+
match super_type {
782+
SuperCallType::TwoArg {
783+
class_arg,
784+
self_arg,
785+
} => {
786+
// 2-arg: load provided arguments
787+
self.compile_expression(class_arg)?;
788+
self.compile_expression(self_arg)?;
789+
}
790+
SuperCallType::ZeroArg => {
791+
// 0-arg: load __class__ cell and first parameter
792+
// Load __class__ from cell/free variable
793+
let scope = self.get_ref_type("__class__").map_err(|e| self.error(e))?;
794+
let idx = match scope {
795+
SymbolScope::Cell => self.get_cell_var_index("__class__")?,
796+
SymbolScope::Free => self.get_free_var_index("__class__")?,
797+
_ => {
798+
return Err(self.error(CodegenErrorType::SyntaxError(
799+
"super(): __class__ cell not found".to_owned(),
800+
)));
801+
}
802+
};
803+
self.emit_arg(idx, Instruction::LoadDeref);
804+
805+
// Load first parameter (typically 'self').
806+
// Safety: can_optimize_super_call() ensures argcount > 0, and
807+
// parameters are always added to varnames first (see symboltable.rs).
808+
let first_param = {
809+
let info = self.code_stack.last().unwrap();
810+
info.metadata.varnames.first().cloned()
811+
};
812+
let first_param = first_param.ok_or_else(|| {
813+
self.error(CodegenErrorType::SyntaxError(
814+
"super(): no arguments and no first parameter".to_owned(),
815+
))
816+
})?;
817+
self.compile_name(&first_param, NameUsage::Load)?;
818+
}
819+
}
820+
Ok(())
821+
}
822+
664823
/// Check if this is an inlined comprehension context (PEP 709)
665824
/// Currently disabled - always returns false to avoid stack issues
666825
fn is_inlined_comprehension_context(&self, _comprehension_type: ComprehensionType) -> bool {
@@ -3357,12 +3516,14 @@ impl Compiler {
33573516
/// Determines if a variable should be CELL or FREE type
33583517
// = get_ref_type
33593518
fn get_ref_type(&self, name: &str) -> Result<SymbolScope, CodegenErrorType> {
3519+
let table = self.symbol_table_stack.last().unwrap();
3520+
33603521
// Special handling for __class__ and __classdict__ in class scope
3361-
if self.ctx.in_class && (name == "__class__" || name == "__classdict__") {
3522+
// This should only apply when we're actually IN a class body,
3523+
// not when we're in a method nested inside a class.
3524+
if table.typ == CompilerScope::Class && (name == "__class__" || name == "__classdict__") {
33623525
return Ok(SymbolScope::Cell);
33633526
}
3364-
3365-
let table = self.symbol_table_stack.last().unwrap();
33663527
match table.lookup(name) {
33673528
Some(symbol) => match symbol.scope {
33683529
SymbolScope::Cell => Ok(SymbolScope::Cell),
@@ -5732,9 +5893,28 @@ impl Compiler {
57325893
};
57335894
}
57345895
Expr::Attribute(ExprAttribute { value, attr, .. }) => {
5735-
self.compile_expression(value)?;
5736-
let idx = self.name(attr.as_str());
5737-
emit!(self, Instruction::LoadAttr { idx });
5896+
// Check for super() attribute access optimization
5897+
if let Some(super_type) = self.can_optimize_super_call(value, attr.as_str()) {
5898+
// super().attr or super(cls, self).attr optimization
5899+
// Stack: [global_super, class, self] → LOAD_SUPER_ATTR → [attr]
5900+
self.load_args_for_super(&super_type)?;
5901+
let idx = self.name(attr.as_str());
5902+
match super_type {
5903+
SuperCallType::TwoArg { .. } => {
5904+
// LoadSuperAttr (pseudo) - will be converted to real LoadSuperAttr
5905+
// with flags=0b10 (has_class=true, load_method=false) in ir.rs
5906+
emit!(self, Instruction::LoadSuperAttr { arg: idx });
5907+
}
5908+
SuperCallType::ZeroArg => {
5909+
emit!(self, Instruction::LoadZeroSuperAttr { idx });
5910+
}
5911+
}
5912+
} else {
5913+
// Normal attribute access
5914+
self.compile_expression(value)?;
5915+
let idx = self.name(attr.as_str());
5916+
emit!(self, Instruction::LoadAttr { idx });
5917+
}
57385918
}
57395919
Expr::Compare(ExprCompare {
57405920
left,
@@ -6159,12 +6339,29 @@ impl Compiler {
61596339
// Method call: obj → LOAD_ATTR_METHOD → [method, self_or_null] → args → CALL
61606340
// Regular call: func → PUSH_NULL → args → CALL
61616341
if let Expr::Attribute(ExprAttribute { value, attr, .. }) = &func {
6162-
// Method call: compile object, then LOAD_ATTR_METHOD
6163-
// LOAD_ATTR_METHOD pushes [method, self_or_null] on stack
6164-
self.compile_expression(value)?;
6165-
let idx = self.name(attr.as_str());
6166-
emit!(self, Instruction::LoadAttrMethod { idx });
6167-
self.compile_call_helper(0, args)?;
6342+
// Check for super() method call optimization
6343+
if let Some(super_type) = self.can_optimize_super_call(value, attr.as_str()) {
6344+
// super().method() or super(cls, self).method() optimization
6345+
// Stack: [global_super, class, self] → LOAD_SUPER_METHOD → [method, self]
6346+
self.load_args_for_super(&super_type)?;
6347+
let idx = self.name(attr.as_str());
6348+
match super_type {
6349+
SuperCallType::TwoArg { .. } => {
6350+
emit!(self, Instruction::LoadSuperMethod { idx });
6351+
}
6352+
SuperCallType::ZeroArg => {
6353+
emit!(self, Instruction::LoadZeroSuperMethod { idx });
6354+
}
6355+
}
6356+
self.compile_call_helper(0, args)?;
6357+
} else {
6358+
// Normal method call: compile object, then LOAD_ATTR_METHOD
6359+
// LOAD_ATTR_METHOD pushes [method, self_or_null] on stack
6360+
self.compile_expression(value)?;
6361+
let idx = self.name(attr.as_str());
6362+
emit!(self, Instruction::LoadAttrMethod { idx });
6363+
self.compile_call_helper(0, args)?;
6364+
}
61686365
} else {
61696366
// Regular call: push func, then NULL for self_or_null slot
61706367
// Stack layout: [func, NULL, args...] - same as method call [func, self, args...]

crates/codegen/src/ir.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use rustpython_compiler_core::{
66
bytecode::{
77
Arg, CodeFlags, CodeObject, CodeUnit, CodeUnits, ConstantData, ExceptionTableEntry,
88
InstrDisplayContext, Instruction, Label, OpArg, PyCodeLocationInfoKind,
9-
encode_exception_table, encode_load_attr_arg,
9+
encode_exception_table, encode_load_attr_arg, encode_load_super_attr_arg,
1010
},
1111
varint::{write_signed_varint, write_varint},
1212
};
@@ -212,6 +212,30 @@ impl CodeInfo {
212212
Instruction::PopBlock => {
213213
info.instr = Instruction::Nop;
214214
}
215+
// LOAD_SUPER_METHOD pseudo → LOAD_SUPER_ATTR (flags=0b11: method=1, class=1)
216+
Instruction::LoadSuperMethod { idx } => {
217+
let encoded = encode_load_super_attr_arg(idx.get(info.arg), true, true);
218+
info.arg = OpArg(encoded);
219+
info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() };
220+
}
221+
// LOAD_ZERO_SUPER_ATTR pseudo → LOAD_SUPER_ATTR (flags=0b00: method=0, class=0)
222+
Instruction::LoadZeroSuperAttr { idx } => {
223+
let encoded = encode_load_super_attr_arg(idx.get(info.arg), false, false);
224+
info.arg = OpArg(encoded);
225+
info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() };
226+
}
227+
// LOAD_ZERO_SUPER_METHOD pseudo → LOAD_SUPER_ATTR (flags=0b01: method=1, class=0)
228+
Instruction::LoadZeroSuperMethod { idx } => {
229+
let encoded = encode_load_super_attr_arg(idx.get(info.arg), true, false);
230+
info.arg = OpArg(encoded);
231+
info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() };
232+
}
233+
// LOAD_SUPER_ATTR → encode with flags=0b10 (method=0, class=1)
234+
Instruction::LoadSuperAttr { arg: idx } => {
235+
let encoded = encode_load_super_attr_arg(idx.get(info.arg), false, true);
236+
info.arg = OpArg(encoded);
237+
info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() };
238+
}
215239
_ => {}
216240
}
217241
}

0 commit comments

Comments
 (0)