X Tutup
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
40cbc39
Emit TO_BOOL before conditional jumps, fix class/module prologue
youknowone Mar 20, 2026
f52276e
Emit MAKE_CELL and COPY_FREE_VARS before RESUME
youknowone Mar 20, 2026
7a9c29d
Emit __static_attributes__ at end of class bodies
youknowone Mar 20, 2026
f8f7fbf
Remove expectedFailure from DictProxyTests iter tests
youknowone Mar 20, 2026
e3fa83e
Use 1-based stack indexing for LIST_EXTEND, SET_UPDATE, etc.
youknowone Mar 20, 2026
18c23e5
Use plain LOAD_ATTR + PUSH_NULL for calls on imported names
youknowone Mar 20, 2026
b2995bc
Duplicate return-None epilogue for fall-through blocks
youknowone Mar 20, 2026
35ac45d
Run cargo fmt on ir.rs
youknowone Mar 20, 2026
1092110
Remove expectedFailure from test_intrinsic_1 in test_dis
youknowone Mar 20, 2026
cc23051
Emit TO_BOOL before conditional jumps for all expressions including C…
youknowone Mar 20, 2026
643861b
Add __classdict__ cell for classes with function definitions
youknowone Mar 21, 2026
a4753e8
Emit __classdictcell__ store in class body epilogue
youknowone Mar 21, 2026
50eaae4
Always run DCE to remove dead code after terminal instructions
youknowone Mar 21, 2026
fdcccad
Restrict LOAD_ATTR plain mode to module/class scope imports
youknowone Mar 21, 2026
83a548e
Eliminate unreachable blocks after jump normalization
youknowone Mar 21, 2026
6a592dd
Fold BUILD_TUPLE 0 into LOAD_CONST empty tuple
youknowone Mar 21, 2026
1d83df2
Handle __classcell__ and __classdictcell__ in type.__new__
youknowone Mar 21, 2026
e1bf8d1
Revert __classdict__ cell and __classdictcell__ changes
youknowone Mar 21, 2026
add881c
Fix unreachable block elimination with fixpoint reachability
youknowone Mar 21, 2026
3181725
Check enclosing scopes for IMPORTED flag in LOAD_ATTR mode
youknowone Mar 22, 2026
c345999
Add __classdict__ cell for classes with function definitions
youknowone Mar 22, 2026
11b4b18
Store __classdictcell__ in class body epilogue
youknowone Mar 22, 2026
0793cb2
Fix clippy collapsible_if warnings and cargo fmt
youknowone Mar 22, 2026
d29b2a9
Revert __classdict__ and __classdictcell__ changes (cause import fail…
youknowone Mar 22, 2026
d5afad9
Revert type.__new__ __classcell__ removal and __classdictcell__ handling
youknowone Mar 22, 2026
c921c8b
Re-add __classdict__ cell and __classdictcell__ store
youknowone Mar 22, 2026
60103dc
Reorder MakeFunctionFlag to match CPython's SET_FUNCTION_ATTRIBUTE
youknowone Mar 22, 2026
560d8ae
Use CPython-compatible power-of-two encoding for SET_FUNCTION_ATTRIBUTE
youknowone Mar 22, 2026
16ce7fb
Remove expectedFailure from test_elim_jump_after_return1 and test_no_…
youknowone Mar 22, 2026
9820993
Remove __classcell__ and __classdictcell__ from class dict in type.__…
youknowone Mar 22, 2026
35a8f7e
Remove expectedFailure from test___classcell___expected_behaviour, ca…
youknowone Mar 22, 2026
e0c7326
Handle MakeCell and CopyFreeVars as no-ops in JIT
youknowone Mar 22, 2026
c958739
Remove expectedFailure from test_load_fast_known_simple
youknowone Mar 23, 2026
926a984
Restore expectedFailure for test_load_fast_known_simple
youknowone Mar 23, 2026
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
3 changes: 0 additions & 3 deletions Lib/test/test_descr.py
Original file line number Diff line number Diff line change
Expand Up @@ -5179,7 +5179,6 @@ def meth(self):
pass
self.C = C

@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
'trace function introduces __local__')
def test_iter_keys(self):
Expand All @@ -5193,7 +5192,6 @@ def test_iter_keys(self):
'__static_attributes__', '__weakref__',
'meth'])

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 5 != 7
@unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
'trace function introduces __local__')
def test_iter_values(self):
Expand All @@ -5203,7 +5201,6 @@ def test_iter_values(self):
values = list(it)
self.assertEqual(len(values), 7)

@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
'trace function introduces __local__')
def test_iter_items(self):
Expand Down
1 change: 0 additions & 1 deletion Lib/test/test_dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -1134,7 +1134,6 @@ def test_kw_names(self):
# Test that value is displayed for keyword argument names:
self.do_disassembly_test(wrap_func_w_kwargs, dis_kw_names)

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_intrinsic_1(self):
# Test that argrepr is displayed for CALL_INTRINSIC_1
self.do_disassembly_test("from math import *", dis_intrinsic_1_2)
Expand Down
3 changes: 1 addition & 2 deletions Lib/test/test_peepholer.py
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,6 @@ def f():
print(i)
self.check_jump_targets(f)

@unittest.expectedFailure # TODO: RUSTPYTHON; 611 JUMP_BACKWARD 16
def test_elim_jump_after_return1(self):
# Eliminate dead code: jumps immediately after returns can't be reached
def f(cond1, cond2):
Expand Down Expand Up @@ -863,7 +862,7 @@ def setUp(self):
self.addCleanup(sys.settrace, sys.gettrace())
sys.settrace(None)

@unittest.expectedFailure # TODO: RUSTPYTHON; BINARY_OP 0 (+)
@unittest.expectedFailure # TODO: RUSTPYTHON; no LOAD_FAST_BORROW_LOAD_FAST_BORROW superinstruction
def test_load_fast_known_simple(self):
def f():
x = 1
Expand Down
1 change: 0 additions & 1 deletion Lib/test/test_super.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,6 @@ def f():

self.assertIs(test_class, A)

@unittest.expectedFailure # TODO: RUSTPYTHON
def test___classcell___expected_behaviour(self):
# See issue #23722
class Meta(type):
Expand Down
2 changes: 0 additions & 2 deletions Lib/test/test_sys_settrace.py
Original file line number Diff line number Diff line change
Expand Up @@ -2063,8 +2063,6 @@ async def test_jump_between_async_with_blocks(output):
async with asynctracecontext(output, 4):
output.append(5)

# TODO: RUSTPYTHON
@unittest.expectedFailure
@jump_test(5, 7, [2, 4], (ValueError, "after"))
def test_no_jump_over_return_out_of_finally_block(output):
try:
Expand Down
145 changes: 108 additions & 37 deletions crates/codegen/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -610,13 +610,13 @@ impl Compiler {
self.compile_expression(value)?;
match collection_type {
CollectionType::List => {
emit!(self, Instruction::ListExtend { i: 0 });
emit!(self, Instruction::ListExtend { i: 1 });
}
CollectionType::Set => {
emit!(self, Instruction::SetUpdate { i: 0 });
emit!(self, Instruction::SetUpdate { i: 1 });
}
CollectionType::Tuple => {
emit!(self, Instruction::ListExtend { i: 0 });
emit!(self, Instruction::ListExtend { i: 1 });
}
}
} else {
Expand All @@ -627,13 +627,13 @@ impl Compiler {
// Sequence already exists, append to it
match collection_type {
CollectionType::List => {
emit!(self, Instruction::ListAppend { i: 0 });
emit!(self, Instruction::ListAppend { i: 1 });
}
CollectionType::Set => {
emit!(self, Instruction::SetAdd { i: 0 });
emit!(self, Instruction::SetAdd { i: 1 });
}
CollectionType::Tuple => {
emit!(self, Instruction::ListAppend { i: 0 });
emit!(self, Instruction::ListAppend { i: 1 });
}
}
} else {
Expand Down Expand Up @@ -692,6 +692,23 @@ impl Compiler {
.expect("symbol_table_stack is empty! This is a compiler bug.")
}

/// Check if a name is imported in current scope or any enclosing scope.
fn is_name_imported(&self, name: &str) -> bool {
if let Some(sym) = self.current_symbol_table().symbols.get(name) {
if sym.flags.contains(SymbolFlags::IMPORTED) {
return true;
} else if sym.scope == SymbolScope::Local {
return false;
}
}
self.symbol_table_stack.iter().rev().skip(1).any(|table| {
table
.symbols
.get(name)
.is_some_and(|sym| sym.flags.contains(SymbolFlags::IMPORTED))
})
}

/// Get the cell-relative index of a free variable.
/// Returns ncells + freevar_idx. Fixed up to localsplus index during finalize.
fn get_free_var_index(&mut self, name: &str) -> CompileResult<oparg::VarNum> {
Expand Down Expand Up @@ -1151,7 +1168,16 @@ impl Compiler {
self.set_qualname();
}

// Emit COPY_FREE_VARS and MAKE_CELL prolog before RESUME
// Emit MAKE_CELL for each cell variable (before RESUME)
{
let ncells = self.code_stack.last().unwrap().metadata.cellvars.len();
for i in 0..ncells {
let i_varnum: oparg::VarNum = u32::try_from(i).expect("too many cellvars").into();
emit!(self, Instruction::MakeCell { i: i_varnum });
}
}

// Emit COPY_FREE_VARS if there are free variables (before RESUME)
{
let nfrees = self.code_stack.last().unwrap().metadata.freevars.len();
if nfrees > 0 {
Expand All @@ -1162,11 +1188,6 @@ impl Compiler {
}
);
}
let ncells = self.code_stack.last().unwrap().metadata.cellvars.len();
for i in 0..ncells {
let i_varnum: oparg::VarNum = u32::try_from(i).expect("too many cellvars").into();
emit!(self, Instruction::MakeCell { i: i_varnum });
}
}

// Emit RESUME (handles async preamble and module lineno 0)
Expand Down Expand Up @@ -1739,7 +1760,7 @@ impl Compiler {
value: value.into(),
});
let doc = self.name("__doc__");
emit!(self, Instruction::StoreGlobal { namei: doc })
emit!(self, Instruction::StoreName { namei: doc })
}

// Handle annotations based on future_annotations flag
Expand Down Expand Up @@ -3424,7 +3445,7 @@ impl Compiler {
if n == 0 {
// Empty handlers (invalid AST) - append rest to list and proceed
// Stack: [prev_exc, orig, list, rest]
emit!(self, Instruction::ListAppend { i: 0 });
emit!(self, Instruction::ListAppend { i: 1 });
// Stack: [prev_exc, orig, list]
emit!(
self,
Expand Down Expand Up @@ -3542,7 +3563,7 @@ impl Compiler {
// After pop: [prev_exc, orig, list, new_rest, lasti] (len=5)
// nth_value(i) = stack[len - i - 1], we need stack[2] = list
// stack[5 - i - 1] = 2 -> i = 2
emit!(self, Instruction::ListAppend { i: 2 });
emit!(self, Instruction::ListAppend { i: 3 });
// Stack: [prev_exc, orig, list, new_rest, lasti]

// POP_TOP - pop lasti
Expand Down Expand Up @@ -3571,7 +3592,7 @@ impl Compiler {
// PEEK(1) = stack[len-1] after pop
// RustPython nth_value(i) = stack[len-i-1] after pop
// For LIST_APPEND 1: stack[len-1] = stack[len-i-1] -> i = 0
emit!(self, Instruction::ListAppend { i: 0 });
emit!(self, Instruction::ListAppend { i: 1 });
// Stack: [prev_exc, orig, list]
emit!(
self,
Expand Down Expand Up @@ -4561,9 +4582,9 @@ impl Compiler {
// 2. Set up class namespace
let (doc_str, body) = split_doc(body, &self.opts);

// Load (global) __name__ and store as __module__
// Load __name__ and store as __module__
let dunder_name = self.name("__name__");
self.emit_load_global(dunder_name, false);
emit!(self, Instruction::LoadName { namei: dunder_name });
let dunder_module = self.name("__module__");
emit!(
self,
Expand All @@ -4584,14 +4605,7 @@ impl Compiler {
}
);

// Store __doc__ only if there's an explicit docstring
if let Some(doc) = doc_str {
self.emit_load_const(ConstantData::Str { value: doc.into() });
let doc_name = self.name("__doc__");
emit!(self, Instruction::StoreName { namei: doc_name });
}

// Store __firstlineno__ (new in Python 3.12+)
// Store __firstlineno__ before __doc__
self.emit_load_const(ConstantData::Integer {
value: BigInt::from(firstlineno),
});
Expand All @@ -4603,6 +4617,13 @@ impl Compiler {
}
);

// Store __doc__ only if there's an explicit docstring
if let Some(doc) = doc_str {
self.emit_load_const(ConstantData::Str { value: doc.into() });
let doc_name = self.name("__doc__");
emit!(self, Instruction::StoreName { namei: doc_name });
}

// Set __type_params__ if we have type parameters
if type_params.is_some() {
// Load .type_params from enclosing scope
Expand Down Expand Up @@ -4661,6 +4682,44 @@ impl Compiler {
.iter()
.position(|var| *var == "__class__");

// Emit __static_attributes__ tuple
{
let attrs: Vec<String> = self
.code_stack
.last()
.unwrap()
.static_attributes
.as_ref()
.map(|s| s.iter().cloned().collect())
.unwrap_or_default();
self.emit_load_const(ConstantData::Tuple {
elements: attrs
.into_iter()
.map(|s| ConstantData::Str { value: s.into() })
.collect(),
});
let static_attrs_name = self.name("__static_attributes__");
emit!(
self,
Instruction::StoreName {
namei: static_attrs_name
}
);
}
Comment on lines +4685 to +4708
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

__static_attributes__ is always serialized as () here.

enter_scope() initializes every class with Some(IndexSet::default()) on Line 1120, but this file never inserts into that set before reaching this block. The new store therefore overwrites every class body’s __static_attributes__ with an empty tuple, including any explicit assignment made in body. Either populate static_attributes before this point or skip the store until the set is non-empty.

Minimal guard to avoid clobbering classes until the collector exists
-        // Emit __static_attributes__ tuple
-        {
-            let attrs: Vec<String> = self
-                .code_stack
-                .last()
-                .unwrap()
-                .static_attributes
-                .as_ref()
-                .map(|s| s.iter().cloned().collect())
-                .unwrap_or_default();
-            self.emit_load_const(ConstantData::Tuple {
-                elements: attrs
-                    .into_iter()
-                    .map(|s| ConstantData::Str { value: s.into() })
-                    .collect(),
-            });
-            let static_attrs_name = self.name("__static_attributes__");
-            emit!(
-                self,
-                Instruction::StoreName {
-                    namei: static_attrs_name
-                }
-            );
-        }
+        if let Some(attrs) = self
+            .code_stack
+            .last()
+            .and_then(|info| info.static_attributes.as_ref())
+            .filter(|attrs| !attrs.is_empty())
+        {
+            self.emit_load_const(ConstantData::Tuple {
+                elements: attrs
+                    .iter()
+                    .cloned()
+                    .map(|s| ConstantData::Str { value: s.into() })
+                    .collect(),
+            });
+            let static_attrs_name = self.name("__static_attributes__");
+            emit!(self, Instruction::StoreName { namei: static_attrs_name });
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/codegen/src/compile.rs` around lines 4626 - 4649, The current block
always emits and stores an empty tuple to __static_attributes__ because
enter_scope() initializes static_attributes to Some(empty) but nothing is added
before this emit; update compile.rs to skip emitting/storing
__static_attributes__ when the collected attrs vector is empty (i.e., check the
code_stack.last().unwrap().static_attributes contents and only call
emit_load_const/Instruction::StoreName/name("__static_attributes__") if attrs is
non-empty), or alternatively change enter_scope() to initialize
static_attributes as None and only create/populate it when the collector runs;
target the symbols static_attributes, enter_scope, emit_load_const,
Instruction::StoreName and name("__static_attributes__") when making the change.


// Store __classdictcell__ if __classdict__ is a cell variable
if self.current_symbol_table().needs_classdict {
let classdict_idx = u32::from(self.get_cell_var_index("__classdict__")?);
emit!(self, PseudoInstruction::LoadClosure { i: classdict_idx });
let classdictcell = self.name("__classdictcell__");
emit!(
self,
Instruction::StoreName {
namei: classdictcell
}
);
}

if let Some(classcell_idx) = classcell_idx {
emit!(
self,
Expand Down Expand Up @@ -4810,11 +4869,11 @@ impl Compiler {
if let ast::Expr::Starred(ast::ExprStarred { value, .. }) = arg {
// Starred: compile and extend
self.compile_expression(value)?;
emit!(self, Instruction::ListExtend { i: 0 });
emit!(self, Instruction::ListExtend { i: 1 });
} else {
// Non-starred: compile and append
self.compile_expression(arg)?;
emit!(self, Instruction::ListAppend { i: 0 });
emit!(self, Instruction::ListAppend { i: 1 });
}
}
}
Expand All @@ -4826,7 +4885,7 @@ impl Compiler {
namei: dot_generic_base
}
);
emit!(self, Instruction::ListAppend { i: 0 });
emit!(self, Instruction::ListAppend { i: 1 });

// Convert list to tuple
emit!(
Expand Down Expand Up @@ -6495,7 +6554,7 @@ impl Compiler {
self.emit_load_const(ConstantData::Integer {
value: annotation_index.into(),
});
emit!(self, Instruction::SetAdd { i: 0 });
emit!(self, Instruction::SetAdd { i: 1 });
emit!(self, Instruction::PopTop);
}
}
Expand Down Expand Up @@ -6742,6 +6801,10 @@ impl Compiler {
_ => {
// Fall back case which always will work!
self.compile_expression(expression)?;
// Compare already produces a bool; everything else needs TO_BOOL
if !matches!(expression, ast::Expr::Compare(_)) {
emit!(self, Instruction::ToBool);
}
Comment on lines +6804 to +6807
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

The Compare rationale is inaccurate.

Rich comparisons can return arbitrary objects; the reason this fast path is safe is the jump opcode’s truthiness handling, not that Compare itself always yields a bool. Please reword the comment so future refactors do not depend on the wrong invariant.

Suggested comment update
-                // Compare already produces a bool; everything else needs TO_BOOL
+                // Rich comparisons may return non-bool objects; skipping TO_BOOL
+                // here is only correct because PopJumpIf* performs truthiness
+                // conversion itself.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Compare already produces a bool; everything else needs TO_BOOL
if !matches!(expression, ast::Expr::Compare(_)) {
emit!(self, Instruction::ToBool);
}
// Rich comparisons may return non-bool objects; skipping TO_BOOL
// here is only correct because PopJumpIf* performs truthiness
// conversion itself.
if !matches!(expression, ast::Expr::Compare(_)) {
emit!(self, Instruction::ToBool);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/codegen/src/compile.rs` around lines 6732 - 6735, The comment above
the ToBool emission is misleading because ast::Expr::Compare may produce
non-bool values; update the comment near the if !matches!(expression,
ast::Expr::Compare) check to state that the Compare fast path is safe due to the
jump opcode’s truthiness handling (not because Compare always yields a bool),
and clarify that everything else needs Instruction::ToBool before branching;
reference the emit!(self, Instruction::ToBool) call and the ast::Expr::Compare
pattern in the comment.

if condition {
emit!(
self,
Expand Down Expand Up @@ -7240,7 +7303,7 @@ impl Compiler {
emit!(
compiler,
Instruction::ListAppend {
i: generators.len().to_u32(),
i: (generators.len() + 1).to_u32(),
}
);
Ok(())
Expand All @@ -7266,7 +7329,7 @@ impl Compiler {
emit!(
compiler,
Instruction::SetAdd {
i: generators.len().to_u32(),
i: (generators.len() + 1).to_u32(),
}
);
Ok(())
Expand Down Expand Up @@ -7298,7 +7361,7 @@ impl Compiler {
emit!(
compiler,
Instruction::MapAdd {
i: generators.len().to_u32(),
i: (generators.len() + 1).to_u32(),
}
);

Expand Down Expand Up @@ -7516,11 +7579,19 @@ impl Compiler {
// CALL at .method( line (not the full expression line)
self.codegen_call_helper(0, args, attr.range())?;
} else {
// Normal method call: compile object, then LOAD_ATTR with method flag
// LOAD_ATTR(method=1) pushes [method, self_or_null] on stack
self.compile_expression(value)?;
let idx = self.name(attr.as_str());
self.emit_load_attr_method(idx);
// Imported names use plain LOAD_ATTR + PUSH_NULL;
// other names use method call mode LOAD_ATTR.
// Check current scope and enclosing scopes for IMPORTED flag.
let is_import = matches!(value.as_ref(), ast::Expr::Name(ast::ExprName { id, .. })
if self.is_name_imported(id.as_str()));
if is_import {
self.emit_load_attr(idx);
emit!(self, Instruction::PushNull);
} else {
self.emit_load_attr_method(idx);
}
self.codegen_call_helper(0, args, call_range)?;
}
} else {
Expand Down Expand Up @@ -7558,7 +7629,7 @@ impl Compiler {
self.compile_expression(&kw.value)?;

if big {
emit!(self, Instruction::MapAdd { i: 0 });
emit!(self, Instruction::MapAdd { i: 1 });
}
}

Expand Down
Loading
Loading
X Tutup