Visitar URL original
Super instructions by youknowone · Pull Request #6694 · RustPython/RustPython · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
super instructions
  • Loading branch information
youknowone committed Jan 11, 2026
commit 52dc491bf5c8de6ca801662d2fa9c43fb2fa9b59
3 changes: 3 additions & 0 deletions Lib/_opcode_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@
'JUMP_IF_NOT_EXC_MATCH': 131,
'SET_EXC_INFO': 134,
'SUBSCRIPT': 135,
'LOAD_SUPER_METHOD': 136,
'LOAD_ZERO_SUPER_ATTR': 137,
'LOAD_ZERO_SUPER_METHOD': 138,
'RESUME': 149,
'JUMP': 252,
'LOAD_CLOSURE': 253,
Expand Down
191 changes: 182 additions & 9 deletions crates/codegen/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,20 @@ pub enum FBlockDatum {
ExceptionName(String),
}

/// Type of super() call optimization detected by can_optimize_super_call()
#[derive(Debug, Clone)]
enum SuperCallType<'a> {
/// super(class, self) - explicit 2-argument form
TwoArg {
class_arg: &'a Expr,
self_arg: &'a Expr,
},
/// super() - implicit 0-argument form (uses __class__ cell)
/// TODO: Enable after proper __class__ cell handling
#[allow(dead_code)]
ZeroArg,
}

#[derive(Debug, Clone)]
pub struct FBlockInfo {
pub fb_type: FBlockType,
Expand Down Expand Up @@ -661,6 +675,129 @@ impl Compiler {
self.symbol_table_stack.pop().expect("compiler bug")
}

/// Check if a super() call can be optimized
/// Returns Some(SuperCallType) if optimization is possible, None otherwise
fn can_optimize_super_call<'a>(
&self,
value: &'a Expr,
attr: &str,
) -> Option<SuperCallType<'a>> {
use ruff_python_ast::*;

// 1. value must be a Call expression
let Expr::Call(ExprCall {
func, arguments, ..
}) = value
else {
return None;
};

// 2. func must be Name("super")
let Expr::Name(ExprName { id, .. }) = func.as_ref() else {
return None;
};
if id.as_str() != "super" {
return None;
}

// 3. attr must not be "__class__"
if attr == "__class__" {
return None;
}

// 4. No keyword arguments
if !arguments.keywords.is_empty() {
return None;
}

// 5. Must be inside a function (not at module level or class body)
if !self.ctx.in_func() {
return None;
}

// 6. "super" must be GlobalImplicit (not redefined locally)
let table = self.current_symbol_table();
if let Some(symbol) = table.lookup("super") {
if symbol.scope != SymbolScope::GlobalImplicit {
return None;
}
} else {
// super not found in symbol table - assume it's a global builtin
}

// 7. Check argument pattern
let args = &arguments.args;

// No starred expressions allowed
if args.iter().any(|arg| matches!(arg, Expr::Starred(_))) {
return None;
}

match args.len() {
2 => {
// 2-arg: super(class, self)
Some(SuperCallType::TwoArg {
class_arg: &args[0],
self_arg: &args[1],
})
}
0 => {
// 0-arg: super() - need __class__ cell and first parameter
// TODO: Enable 0-arg super() optimization after proper __class__ cell handling
// For now, skip optimization to avoid issues with nested class definitions
// and other complex scenarios where __class__ might not be properly available
None
}
_ => None, // 1 or 3+ args - not optimizable
}
}
Comment on lines +676 to +773

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Search for load_args_for_super to verify Cell handling
rg -n "load_args_for_super" crates/codegen/src/compile.rs -A 30 -B 2

Repository: RustPython/RustPython

Length of output: 5185


🏁 Script executed:

# Search for SymbolScope and SymbolFlags definitions
rg -n "enum SymbolScope|SymbolScope::" crates/codegen/src -A 15 | head -100

Repository: RustPython/RustPython

Length of output: 7084


🏁 Script executed:

# Check how __class__ is handled throughout the codebase
rg -n "__class__" crates/codegen/src/compile.rs -B 2 -A 2

Repository: RustPython/RustPython

Length of output: 2935


🏁 Script executed:

# Look for Cell scope usage in symbol handling
rg -n "SymbolScope::Cell" crates/codegen/src

Repository: RustPython/RustPython

Length of output: 1106


Super-call eligibility checks: align __class__ scope handling with load_args_for_super().

The 0-arg super() path rejects __class__ unless it's Free or has the FREE_CLASS flag (lines 759–760), but load_args_for_super() explicitly handles SymbolScope::Cell (line 795). This inconsistency may unnecessarily block valid optimizations when __class__ is scoped as Cell in the method.

Proposed fix
                 if let Some(symbol) = table.lookup("__class__") {
-                    if symbol.scope != SymbolScope::Free
-                        && !symbol.flags.contains(SymbolFlags::FREE_CLASS)
+                    if !matches!(symbol.scope, SymbolScope::Free | SymbolScope::Cell)
+                        && !symbol.flags.contains(SymbolFlags::FREE_CLASS)
                     {
                         return None;
                     }
🤖 Prompt for AI Agents
In @crates/codegen/src/compile.rs around lines 676 - 773,
can_optimize_super_call rejects 0-arg super() when __class__ is not
SymbolScope::Free or lacks FREE_CLASS, but load_args_for_super also accepts
SymbolScope::Cell; update can_optimize_super_call to treat SymbolScope::Cell as
acceptable (i.e., allow optimization when table.lookup("__class__") returns a
symbol whose scope is Free or Cell or whose flags contain FREE_CLASS) so
eligibility matches load_args_for_super; reference functions
can_optimize_super_call and load_args_for_super and the symbol/name "__class__"
and SymbolScope::Cell/FREE_CLASS.


/// Load arguments for super() optimization onto the stack
/// Stack result: [global_super, class, self]
fn load_args_for_super(&mut self, super_type: &SuperCallType<'_>) -> CompileResult<()> {
// 1. Load global super
self.compile_name("super", NameUsage::Load)?;

match super_type {
SuperCallType::TwoArg {
class_arg,
self_arg,
} => {
// 2-arg: load provided arguments
self.compile_expression(class_arg)?;
self.compile_expression(self_arg)?;
}
SuperCallType::ZeroArg => {
// 0-arg: load __class__ cell and first parameter
// Load __class__ from cell/free variable
let scope = self.get_ref_type("__class__").map_err(|e| self.error(e))?;
let idx = match scope {
SymbolScope::Cell => self.get_cell_var_index("__class__")?,
SymbolScope::Free => self.get_free_var_index("__class__")?,
_ => {
return Err(self.error(CodegenErrorType::SyntaxError(
"super(): __class__ cell not found".to_owned(),
)));
}
};
self.emit_arg(idx, Instruction::LoadDeref);

// Load first parameter (typically 'self')
let first_param = {
let info = self.code_stack.last().unwrap();
info.metadata.varnames.first().cloned()
};
let first_param = first_param.ok_or_else(|| {
self.error(CodegenErrorType::SyntaxError(
"super(): no arguments and no first parameter".to_owned(),
))
})?;
self.compile_name(&first_param, NameUsage::Load)?;
}
}
Ok(())
}

/// Check if this is an inlined comprehension context (PEP 709)
/// Currently disabled - always returns false to avoid stack issues
fn is_inlined_comprehension_context(&self, _comprehension_type: ComprehensionType) -> bool {
Expand Down Expand Up @@ -5732,9 +5869,28 @@ impl Compiler {
};
}
Expr::Attribute(ExprAttribute { value, attr, .. }) => {
self.compile_expression(value)?;
let idx = self.name(attr.as_str());
emit!(self, Instruction::LoadAttr { idx });
// Check for super() attribute access optimization
if let Some(super_type) = self.can_optimize_super_call(value, attr.as_str()) {
// super().attr or super(cls, self).attr optimization
// Stack: [global_super, class, self] → LOAD_SUPER_ATTR → [attr]
self.load_args_for_super(&super_type)?;
let idx = self.name(attr.as_str());
match super_type {
SuperCallType::TwoArg { .. } => {
// LoadSuperAttr (pseudo) - will be converted to real LoadSuperAttr
// with flags=0b10 (has_class=true, load_method=false) in ir.rs
emit!(self, Instruction::LoadSuperAttr { arg: idx });
}
SuperCallType::ZeroArg => {
emit!(self, Instruction::LoadZeroSuperAttr { idx });
}
}
} else {
// Normal attribute access
self.compile_expression(value)?;
let idx = self.name(attr.as_str());
emit!(self, Instruction::LoadAttr { idx });
}
}
Expr::Compare(ExprCompare {
left,
Expand Down Expand Up @@ -6159,12 +6315,29 @@ impl Compiler {
// Method call: obj → LOAD_ATTR_METHOD → [method, self_or_null] → args → CALL
// Regular call: func → PUSH_NULL → args → CALL
if let Expr::Attribute(ExprAttribute { value, attr, .. }) = &func {
// Method call: compile object, then LOAD_ATTR_METHOD
// LOAD_ATTR_METHOD pushes [method, self_or_null] on stack
self.compile_expression(value)?;
let idx = self.name(attr.as_str());
emit!(self, Instruction::LoadAttrMethod { idx });
self.compile_call_helper(0, args)?;
// Check for super() method call optimization
if let Some(super_type) = self.can_optimize_super_call(value, attr.as_str()) {
// super().method() or super(cls, self).method() optimization
// Stack: [global_super, class, self] → LOAD_SUPER_METHOD → [method, self]
self.load_args_for_super(&super_type)?;
let idx = self.name(attr.as_str());
match super_type {
SuperCallType::TwoArg { .. } => {
emit!(self, Instruction::LoadSuperMethod { idx });
}
SuperCallType::ZeroArg => {
emit!(self, Instruction::LoadZeroSuperMethod { idx });
}
}
self.compile_call_helper(0, args)?;
} else {
// Normal method call: compile object, then LOAD_ATTR_METHOD
// LOAD_ATTR_METHOD pushes [method, self_or_null] on stack
self.compile_expression(value)?;
let idx = self.name(attr.as_str());
emit!(self, Instruction::LoadAttrMethod { idx });
self.compile_call_helper(0, args)?;
}
} else {
// Regular call: push func, then NULL for self_or_null slot
// Stack layout: [func, NULL, args...] - same as method call [func, self, args...]
Expand Down
26 changes: 25 additions & 1 deletion crates/codegen/src/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use rustpython_compiler_core::{
bytecode::{
Arg, CodeFlags, CodeObject, CodeUnit, CodeUnits, ConstantData, ExceptionTableEntry,
InstrDisplayContext, Instruction, Label, OpArg, PyCodeLocationInfoKind,
encode_exception_table, encode_load_attr_arg,
encode_exception_table, encode_load_attr_arg, encode_load_super_attr_arg,
},
varint::{write_signed_varint, write_varint},
};
Expand Down Expand Up @@ -212,6 +212,30 @@ impl CodeInfo {
Instruction::PopBlock => {
info.instr = Instruction::Nop;
}
// LOAD_SUPER_METHOD pseudo → LOAD_SUPER_ATTR (flags=0b11: method=1, class=1)
Instruction::LoadSuperMethod { idx } => {
let encoded = encode_load_super_attr_arg(idx.get(info.arg), true, true);
info.arg = OpArg(encoded);
info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() };
}
// LOAD_ZERO_SUPER_ATTR pseudo → LOAD_SUPER_ATTR (flags=0b00: method=0, class=0)
Instruction::LoadZeroSuperAttr { idx } => {
let encoded = encode_load_super_attr_arg(idx.get(info.arg), false, false);
info.arg = OpArg(encoded);
info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() };
}
// LOAD_ZERO_SUPER_METHOD pseudo → LOAD_SUPER_ATTR (flags=0b01: method=1, class=0)
Instruction::LoadZeroSuperMethod { idx } => {
let encoded = encode_load_super_attr_arg(idx.get(info.arg), true, false);
info.arg = OpArg(encoded);
info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() };
}
// LOAD_SUPER_ATTR → encode with flags=0b10 (method=0, class=1)
Instruction::LoadSuperAttr { arg: idx } => {
let encoded = encode_load_super_attr_arg(idx.get(info.arg), false, true);
info.arg = OpArg(encoded);
info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() };
}
_ => {}
}
}
Expand Down
15 changes: 15 additions & 0 deletions crates/compiler-core/src/bytecode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,21 @@ pub const fn decode_load_attr_arg(oparg: u32) -> (u32, bool) {
(name_idx, is_method)
}

/// Encode LOAD_SUPER_ATTR oparg: bit 0 = load_method, bit 1 = has_class, bits 2+ = name index.
#[inline]
pub const fn encode_load_super_attr_arg(name_idx: u32, load_method: bool, has_class: bool) -> u32 {
(name_idx << 2) | ((has_class as u32) << 1) | (load_method as u32)
}

/// Decode LOAD_SUPER_ATTR oparg: returns (name_idx, load_method, has_class).
#[inline]
pub const fn decode_load_super_attr_arg(oparg: u32) -> (u32, bool, bool) {
let load_method = (oparg & 1) == 1;
let has_class = (oparg & 2) == 2;
let name_idx = oparg >> 2;
(name_idx, load_method, has_class)
}

/// Oparg values for [`Instruction::ConvertValue`].
///
/// ## See also
Expand Down
36 changes: 35 additions & 1 deletion crates/compiler-core/src/bytecode/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ pub enum Instruction {
LoadName(Arg<NameIdx>) = 92,
LoadSuperAttr {
arg: Arg<u32>,
} = 93, // Placeholder
} = 93,
MakeCell(Arg<NameIdx>) = 94, // Placeholder
MapAdd {
i: Arg<u32>,
Expand Down Expand Up @@ -243,6 +243,17 @@ pub enum Instruction {
Resume {
arg: Arg<u32>,
} = 149,
// ===== LOAD_SUPER_* Pseudo Opcodes (136-138) =====
// These are converted to LoadSuperAttr during bytecode finalization
LoadSuperMethod {
idx: Arg<NameIdx>,
} = 136, // CPython uses pseudo-op 260
LoadZeroSuperAttr {
idx: Arg<NameIdx>,
} = 137, // CPython uses pseudo-op 261
LoadZeroSuperMethod {
idx: Arg<NameIdx>,
} = 138, // CPython uses pseudo-op 262
Comment thread
coderabbitai[bot] marked this conversation as resolved.
// ==================== RustPython-only instructions (119-135) ====================
// Ideally, we want to be fully aligned with CPython opcodes, but we still have some leftovers.
// So we assign random IDs to these opcodes.
Expand Down Expand Up @@ -588,6 +599,16 @@ impl Instruction {
Self::UnaryNot => 0,
Self::GetYieldFromIter => 0,
Self::PushNull => 1, // Push NULL for call protocol
// LoadSuperAttr: pop [super, class, self], push [attr] or [method, self_or_null]
// stack_effect depends on load_method flag (bit 0 of oparg)
Self::LoadSuperAttr { arg: idx } => {
let (_, load_method, _) = decode_load_super_attr_arg(idx.get(arg));
if load_method { -3 + 2 } else { -3 + 1 }
}
// Pseudo instructions (calculated before conversion)
Self::LoadSuperMethod { .. } => -3 + 2, // pop 3, push [method, self_or_null]
Self::LoadZeroSuperAttr { .. } => -3 + 1, // pop 3, push [attr]
Self::LoadZeroSuperMethod { .. } => -3 + 2, // pop 3, push [method, self_or_null]
Self::Cache => 0,
Self::BinarySlice => 0,
Self::BinaryOpInplaceAddUnicode => 0,
Expand Down Expand Up @@ -768,6 +789,19 @@ impl Instruction {
Self::LoadFastAndClear(idx) => w!(LOAD_FAST_AND_CLEAR, varname = idx),
Self::LoadGlobal(idx) => w!(LOAD_GLOBAL, name = idx),
Self::LoadName(idx) => w!(LOAD_NAME, name = idx),
Self::LoadSuperAttr { arg: idx } => {
let encoded = idx.get(arg);
let (name_idx, load_method, has_class) = decode_load_super_attr_arg(encoded);
let attr_name = name(name_idx);
write!(
f,
"{:pad$}({}, {}, method={}, class={})",
"LOAD_SUPER_ATTR", encoded, attr_name, load_method, has_class
)
}
Self::LoadSuperMethod { idx } => w!(LOAD_SUPER_METHOD, name = idx),
Self::LoadZeroSuperAttr { idx } => w!(LOAD_ZERO_SUPER_ATTR, name = idx),
Self::LoadZeroSuperMethod { idx } => w!(LOAD_ZERO_SUPER_METHOD, name = idx),
Self::MakeFunction => w!(MAKE_FUNCTION),
Self::MapAdd { i } => w!(MAP_ADD, i),
Self::MatchClass(arg) => w!(MATCH_CLASS, arg),
Expand Down
Loading