From 40cbc3963ce58d8c1888e9f2b45706f6421b91fc Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Fri, 20 Mar 2026 17:08:12 +0900 Subject: [PATCH 01/34] Emit TO_BOOL before conditional jumps, fix class/module prologue - Emit TO_BOOL before POP_JUMP_IF_TRUE/FALSE in the general case of compile_jump_if (Compare expressions excluded since they already produce a bool) - Module-level __doc__: use STORE_NAME instead of STORE_GLOBAL - Class body __module__: use LOAD_NAME instead of LOAD_GLOBAL - Class body: store __firstlineno__ before __doc__ --- crates/codegen/src/compile.rs | 26 +++++++------ ...thon_codegen__compile__tests__if_ands.snap | 30 ++++++++++----- ...hon_codegen__compile__tests__if_mixed.snap | 38 +++++++++++++------ ...ython_codegen__compile__tests__if_ors.snap | 30 ++++++++++----- 4 files changed, 84 insertions(+), 40 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 3cf9fb7fd6f..1143a8b6286 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -1739,7 +1739,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 @@ -4561,9 +4561,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, @@ -4584,14 +4584,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), }); @@ -4603,6 +4596,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 @@ -6742,6 +6742,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); + } if condition { emit!( self, diff --git a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ands.snap b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ands.snap index 9dd78c6b7b2..ef45babe880 100644 --- a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ands.snap +++ b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ands.snap @@ -1,21 +1,33 @@ --- source: crates/codegen/src/compile.rs -assertion_line: 9100 +assertion_line: 9266 expression: "compile_exec(\"\\\nif True and False and False:\n pass\n\")" --- 1 0 RESUME (0) >> 1 LOAD_CONST (True) - 2 POP_JUMP_IF_FALSE (9) + 2 TO_BOOL 3 CACHE - 4 NOT_TAKEN - >> 5 LOAD_CONST (False) - 6 POP_JUMP_IF_FALSE (5) + 4 CACHE + 5 CACHE + 6 POP_JUMP_IF_FALSE (17) 7 CACHE 8 NOT_TAKEN >> 9 LOAD_CONST (False) - 10 POP_JUMP_IF_FALSE (1) + 10 TO_BOOL 11 CACHE - 12 NOT_TAKEN + 12 CACHE + 13 CACHE + 14 POP_JUMP_IF_FALSE (9) + 15 CACHE + 16 NOT_TAKEN + >> 17 LOAD_CONST (False) + 18 TO_BOOL + 19 CACHE + 20 CACHE + 21 CACHE + 22 POP_JUMP_IF_FALSE (1) + 23 CACHE + 24 NOT_TAKEN - 2 13 LOAD_CONST (None) - 14 RETURN_VALUE + 2 25 LOAD_CONST (None) + 26 RETURN_VALUE diff --git a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_mixed.snap b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_mixed.snap index e9c3ad8a3c6..1ea4f471138 100644 --- a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_mixed.snap +++ b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_mixed.snap @@ -1,25 +1,41 @@ --- source: crates/codegen/src/compile.rs -assertion_line: 9110 +assertion_line: 9276 expression: "compile_exec(\"\\\nif (True and False) or (False and True):\n pass\n\")" --- 1 0 RESUME (0) >> 1 LOAD_CONST (True) - 2 POP_JUMP_IF_FALSE (5) + 2 TO_BOOL 3 CACHE - 4 NOT_TAKEN - >> 5 LOAD_CONST (False) - 6 POP_JUMP_IF_TRUE (9) + 4 CACHE + 5 CACHE + 6 POP_JUMP_IF_FALSE (9) 7 CACHE 8 NOT_TAKEN >> 9 LOAD_CONST (False) - 10 POP_JUMP_IF_FALSE (5) + 10 TO_BOOL 11 CACHE - 12 NOT_TAKEN - 13 LOAD_CONST (True) - 14 POP_JUMP_IF_FALSE (1) + 12 CACHE + 13 CACHE + 14 POP_JUMP_IF_TRUE (17) 15 CACHE 16 NOT_TAKEN + >> 17 LOAD_CONST (False) + 18 TO_BOOL + 19 CACHE + 20 CACHE + 21 CACHE + 22 POP_JUMP_IF_FALSE (9) + 23 CACHE + 24 NOT_TAKEN + 25 LOAD_CONST (True) + 26 TO_BOOL + 27 CACHE + 28 CACHE + 29 CACHE + 30 POP_JUMP_IF_FALSE (1) + 31 CACHE + 32 NOT_TAKEN - 2 17 LOAD_CONST (None) - 18 RETURN_VALUE + 2 33 LOAD_CONST (None) + 34 RETURN_VALUE diff --git a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ors.snap b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ors.snap index 83212144b99..f8a18d66e31 100644 --- a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ors.snap +++ b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ors.snap @@ -1,21 +1,33 @@ --- source: crates/codegen/src/compile.rs -assertion_line: 9090 +assertion_line: 9256 expression: "compile_exec(\"\\\nif True or False or False:\n pass\n\")" --- 1 0 RESUME (0) >> 1 LOAD_CONST (True) - 2 POP_JUMP_IF_TRUE (9) + 2 TO_BOOL 3 CACHE - 4 NOT_TAKEN - >> 5 LOAD_CONST (False) - 6 POP_JUMP_IF_TRUE (5) + 4 CACHE + 5 CACHE + 6 POP_JUMP_IF_TRUE (17) 7 CACHE 8 NOT_TAKEN >> 9 LOAD_CONST (False) - 10 POP_JUMP_IF_FALSE (1) + 10 TO_BOOL 11 CACHE - 12 NOT_TAKEN + 12 CACHE + 13 CACHE + 14 POP_JUMP_IF_TRUE (9) + 15 CACHE + 16 NOT_TAKEN + >> 17 LOAD_CONST (False) + 18 TO_BOOL + 19 CACHE + 20 CACHE + 21 CACHE + 22 POP_JUMP_IF_FALSE (1) + 23 CACHE + 24 NOT_TAKEN - 2 13 LOAD_CONST (None) - 14 RETURN_VALUE + 2 25 LOAD_CONST (None) + 26 RETURN_VALUE From f52276ec7902aa9b501a254ceae9feec08f2e7a8 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Fri, 20 Mar 2026 17:20:37 +0900 Subject: [PATCH 02/34] Emit MAKE_CELL and COPY_FREE_VARS before RESUME Emit MAKE_CELL for each cell variable and COPY_FREE_VARS N for free variables at the start of each code object, before RESUME. These instructions are no-ops in the VM but align the bytecode with CPython 3.14's output. --- crates/codegen/src/compile.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 1143a8b6286..d0a4412a22c 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -1151,7 +1151,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 { @@ -1162,11 +1171,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) From 7a9c29db2e5a0b9726f8faed6e394a3db6aaf975 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Fri, 20 Mar 2026 17:30:18 +0900 Subject: [PATCH 03/34] Emit __static_attributes__ at end of class bodies Store a tuple of attribute names (currently always empty) as __static_attributes__ in the class namespace, matching CPython 3.14's class body epilogue. Attribute name collection from self.xxx accesses is a follow-up task. --- crates/codegen/src/compile.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index d0a4412a22c..bc2459c1952 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -4665,6 +4665,31 @@ impl Compiler { .iter() .position(|var| *var == "__class__"); + // Emit __static_attributes__ tuple + { + let attrs: Vec = 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(classcell_idx) = classcell_idx { emit!( self, From f8f7fbfbecddaa062b9f67bc15ae522a6894e9fc Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Fri, 20 Mar 2026 20:31:31 +0900 Subject: [PATCH 04/34] Remove expectedFailure from DictProxyTests iter tests test_iter_keys, test_iter_values, test_iter_items now pass because class bodies emit __static_attributes__ and __firstlineno__, matching the expected dict key set. --- Lib/test/test_descr.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 7988f946cab..8a31d8ea092 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -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): @@ -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): @@ -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): From e3fa83e6dd3a2be6e56095f2a5bb733f0b0118b0 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Fri, 20 Mar 2026 21:23:23 +0900 Subject: [PATCH 05/34] Use 1-based stack indexing for LIST_EXTEND, SET_UPDATE, etc. Switch LIST_APPEND, LIST_EXTEND, SET_ADD, SET_UPDATE, MAP_ADD from 0-based to 1-based stack depth argument, matching CPython's PEEK(oparg) convention. Adjust the VM to subtract 1 before calling nth_value. --- crates/codegen/src/compile.rs | 34 +++++++++++++++++----------------- crates/vm/src/frame.rs | 10 +++++----- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index bc2459c1952..afa035f93bc 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -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 { @@ -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 { @@ -3428,7 +3428,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, @@ -3546,7 +3546,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 @@ -3575,7 +3575,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, @@ -4839,11 +4839,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 }); } } } @@ -4855,7 +4855,7 @@ impl Compiler { namei: dot_generic_base } ); - emit!(self, Instruction::ListAppend { i: 0 }); + emit!(self, Instruction::ListAppend { i: 1 }); // Convert list to tuple emit!( @@ -6524,7 +6524,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); } } @@ -7273,7 +7273,7 @@ impl Compiler { emit!( compiler, Instruction::ListAppend { - i: generators.len().to_u32(), + i: (generators.len() + 1).to_u32(), } ); Ok(()) @@ -7299,7 +7299,7 @@ impl Compiler { emit!( compiler, Instruction::SetAdd { - i: generators.len().to_u32(), + i: (generators.len() + 1).to_u32(), } ); Ok(()) @@ -7331,7 +7331,7 @@ impl Compiler { emit!( compiler, Instruction::MapAdd { - i: generators.len().to_u32(), + i: (generators.len() + 1).to_u32(), } ); @@ -7591,7 +7591,7 @@ impl Compiler { self.compile_expression(&kw.value)?; if big { - emit!(self, Instruction::MapAdd { i: 0 }); + emit!(self, Instruction::MapAdd { i: 1 }); } } diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index fef682ff686..c38c6da11ad 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -2623,7 +2623,7 @@ impl ExecutingFrame<'_> { } Instruction::ListAppend { i } => { let item = self.pop_value(); - let obj = self.nth_value(i.get(arg)); + let obj = self.nth_value(i.get(arg) - 1); let list: &Py = unsafe { // SAFETY: trust compiler obj.downcast_unchecked_ref() @@ -2633,7 +2633,7 @@ impl ExecutingFrame<'_> { } Instruction::ListExtend { i } => { let iterable = self.pop_value(); - let obj = self.nth_value(i.get(arg)); + let obj = self.nth_value(i.get(arg) - 1); let list: &Py = unsafe { // SAFETY: compiler guarantees correct type obj.downcast_unchecked_ref() @@ -2977,7 +2977,7 @@ impl ExecutingFrame<'_> { Instruction::MapAdd { i } => { let value = self.pop_value(); let key = self.pop_value(); - let obj = self.nth_value(i.get(arg)); + let obj = self.nth_value(i.get(arg) - 1); let dict: &Py = unsafe { // SAFETY: trust compiler obj.downcast_unchecked_ref() @@ -3308,7 +3308,7 @@ impl ExecutingFrame<'_> { } Instruction::SetAdd { i } => { let item = self.pop_value(); - let obj = self.nth_value(i.get(arg)); + let obj = self.nth_value(i.get(arg) - 1); let set: &Py = unsafe { // SAFETY: trust compiler obj.downcast_unchecked_ref() @@ -3318,7 +3318,7 @@ impl ExecutingFrame<'_> { } Instruction::SetUpdate { i } => { let iterable = self.pop_value(); - let obj = self.nth_value(i.get(arg)); + let obj = self.nth_value(i.get(arg) - 1); let set: &Py = unsafe { // SAFETY: compiler guarantees correct type obj.downcast_unchecked_ref() From 18c23e595b2d62738a9aeff451a2390591aa6222 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Fri, 20 Mar 2026 22:21:17 +0900 Subject: [PATCH 06/34] Use plain LOAD_ATTR + PUSH_NULL for calls on imported names When the call target is an attribute of an imported name (e.g., logging.getLogger()), use plain LOAD_ATTR (method_flag=0) with a separate PUSH_NULL instead of method-mode LOAD_ATTR. This matches CPython 3.14's behavior which avoids the method call optimization for module attribute access. --- crates/codegen/src/compile.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index afa035f93bc..53d092ac6df 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -7549,11 +7549,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 (modules) use plain LOAD_ATTR + PUSH_NULL; + // other names use method call mode LOAD_ATTR + let is_import = matches!(value.as_ref(), ast::Expr::Name(ast::ExprName { id, .. }) + if self.current_symbol_table().symbols.get(id.as_str()) + .is_some_and(|s| s.flags.contains(SymbolFlags::IMPORTED))); + 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 { From b2995bc143dfd6f5a3b761418f5fc5139d737ce2 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Fri, 20 Mar 2026 22:46:41 +0900 Subject: [PATCH 07/34] Duplicate return-None epilogue for fall-through blocks When the last block in a code object is exactly LOAD_CONST None + RETURN_VALUE (the implicit return), duplicate these instructions into blocks that would otherwise fall through to it. This matches CPython 3.14's behavior of giving each code path its own explicit return instruction. --- crates/codegen/src/ir.rs | 63 +++++++++++++++++++ ...thon_codegen__compile__tests__if_ands.snap | 22 ++++--- ...hon_codegen__compile__tests__if_mixed.snap | 14 +++-- ...ython_codegen__compile__tests__if_ors.snap | 10 +-- 4 files changed, 89 insertions(+), 20 deletions(-) diff --git a/crates/codegen/src/ir.rs b/crates/codegen/src/ir.rs index 6be851e15b5..4306476303c 100644 --- a/crates/codegen/src/ir.rs +++ b/crates/codegen/src/ir.rs @@ -208,6 +208,7 @@ impl CodeInfo { label_exception_targets(&mut self.blocks); push_cold_blocks_to_end(&mut self.blocks); normalize_jumps(&mut self.blocks); + duplicate_end_returns(&mut self.blocks); self.optimize_load_global_push_null(); let max_stackdepth = self.max_stackdepth()?; @@ -1647,6 +1648,68 @@ fn normalize_jumps(blocks: &mut [Block]) { } } +/// Duplicate `LOAD_CONST None + RETURN_VALUE` for blocks that fall through +/// to the final return block. Matches CPython's behavior of ensuring every +/// code path that reaches the end of a function/module has its own explicit +/// return instruction. +fn duplicate_end_returns(blocks: &mut [Block]) { + // Walk the block chain to find the last block + let mut last_block = BlockIdx(0); + let mut current = BlockIdx(0); + while current != BlockIdx::NULL { + last_block = current; + current = blocks[current.idx()].next; + } + + // Check if the last block ends with LOAD_CONST + RETURN_VALUE (the implicit return) + let last_insts = &blocks[last_block.idx()].instructions; + // Only apply when the last block is EXACTLY a return-None epilogue + let is_return_block = last_insts.len() == 2 + && matches!( + last_insts[0].instr, + AnyInstruction::Real(Instruction::LoadConst { .. }) + ) + && matches!( + last_insts[1].instr, + AnyInstruction::Real(Instruction::ReturnValue) + ); + if !is_return_block { + return; + } + + // Get the return instructions to clone + let return_insts: Vec = last_insts[last_insts.len() - 2..].to_vec(); + + // Find non-cold blocks that fall through to the last block + let mut blocks_to_fix = Vec::new(); + current = BlockIdx(0); + while current != BlockIdx::NULL { + let block = &blocks[current.idx()]; + if current != last_block + && block.next == last_block + && !block.cold + && !block.except_handler + { + let has_fallthrough = block + .instructions + .last() + .map(|ins| !ins.instr.is_scope_exit() && !ins.instr.is_unconditional_jump()) + .unwrap_or(true); + if has_fallthrough { + blocks_to_fix.push(current); + } + } + current = blocks[current.idx()].next; + } + + // Duplicate the return instructions at the end of fall-through blocks + for block_idx in blocks_to_fix { + blocks[block_idx.idx()] + .instructions + .extend_from_slice(&return_insts); + } +} + /// Label exception targets: walk CFG with except stack, set per-instruction /// handler info and block preserve_lasti flag. Converts POP_BLOCK to NOP. /// flowgraph.c label_exception_targets + push_except_block diff --git a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ands.snap b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ands.snap index ef45babe880..6eea20c54e9 100644 --- a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ands.snap +++ b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ands.snap @@ -1,33 +1,35 @@ --- source: crates/codegen/src/compile.rs -assertion_line: 9266 +assertion_line: 9317 expression: "compile_exec(\"\\\nif True and False and False:\n pass\n\")" --- 1 0 RESUME (0) - >> 1 LOAD_CONST (True) + 1 LOAD_CONST (True) 2 TO_BOOL - 3 CACHE + >> 3 CACHE 4 CACHE 5 CACHE - 6 POP_JUMP_IF_FALSE (17) + 6 POP_JUMP_IF_FALSE (19) 7 CACHE 8 NOT_TAKEN - >> 9 LOAD_CONST (False) + 9 LOAD_CONST (False) 10 TO_BOOL - 11 CACHE + >> 11 CACHE 12 CACHE 13 CACHE - 14 POP_JUMP_IF_FALSE (9) + 14 POP_JUMP_IF_FALSE (11) 15 CACHE 16 NOT_TAKEN - >> 17 LOAD_CONST (False) + 17 LOAD_CONST (False) 18 TO_BOOL - 19 CACHE + >> 19 CACHE 20 CACHE 21 CACHE - 22 POP_JUMP_IF_FALSE (1) + 22 POP_JUMP_IF_FALSE (3) 23 CACHE 24 NOT_TAKEN 2 25 LOAD_CONST (None) 26 RETURN_VALUE + 27 LOAD_CONST (None) + 28 RETURN_VALUE diff --git a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_mixed.snap b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_mixed.snap index 1ea4f471138..b6d5edda048 100644 --- a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_mixed.snap +++ b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_mixed.snap @@ -1,12 +1,12 @@ --- source: crates/codegen/src/compile.rs -assertion_line: 9276 +assertion_line: 9327 expression: "compile_exec(\"\\\nif (True and False) or (False and True):\n pass\n\")" --- 1 0 RESUME (0) - >> 1 LOAD_CONST (True) + 1 LOAD_CONST (True) 2 TO_BOOL - 3 CACHE + >> 3 CACHE 4 CACHE 5 CACHE 6 POP_JUMP_IF_FALSE (9) @@ -14,7 +14,7 @@ expression: "compile_exec(\"\\\nif (True and False) or (False and True):\n pa 8 NOT_TAKEN >> 9 LOAD_CONST (False) 10 TO_BOOL - 11 CACHE + >> 11 CACHE 12 CACHE 13 CACHE 14 POP_JUMP_IF_TRUE (17) @@ -25,7 +25,7 @@ expression: "compile_exec(\"\\\nif (True and False) or (False and True):\n pa 19 CACHE 20 CACHE 21 CACHE - 22 POP_JUMP_IF_FALSE (9) + 22 POP_JUMP_IF_FALSE (11) 23 CACHE 24 NOT_TAKEN 25 LOAD_CONST (True) @@ -33,9 +33,11 @@ expression: "compile_exec(\"\\\nif (True and False) or (False and True):\n pa 27 CACHE 28 CACHE 29 CACHE - 30 POP_JUMP_IF_FALSE (1) + 30 POP_JUMP_IF_FALSE (3) 31 CACHE 32 NOT_TAKEN 2 33 LOAD_CONST (None) 34 RETURN_VALUE + 35 LOAD_CONST (None) + 36 RETURN_VALUE diff --git a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ors.snap b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ors.snap index f8a18d66e31..52d8f1ac0b3 100644 --- a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ors.snap +++ b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ors.snap @@ -1,12 +1,12 @@ --- source: crates/codegen/src/compile.rs -assertion_line: 9256 +assertion_line: 9307 expression: "compile_exec(\"\\\nif True or False or False:\n pass\n\")" --- 1 0 RESUME (0) - >> 1 LOAD_CONST (True) + 1 LOAD_CONST (True) 2 TO_BOOL - 3 CACHE + >> 3 CACHE 4 CACHE 5 CACHE 6 POP_JUMP_IF_TRUE (17) @@ -25,9 +25,11 @@ expression: "compile_exec(\"\\\nif True or False or False:\n pass\n\")" 19 CACHE 20 CACHE 21 CACHE - 22 POP_JUMP_IF_FALSE (1) + 22 POP_JUMP_IF_FALSE (3) 23 CACHE 24 NOT_TAKEN 2 25 LOAD_CONST (None) 26 RETURN_VALUE + 27 LOAD_CONST (None) + 28 RETURN_VALUE From 35ac45d179bc1901518e758e0af92f947a672414 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Fri, 20 Mar 2026 23:38:13 +0900 Subject: [PATCH 08/34] Run cargo fmt on ir.rs --- crates/codegen/src/ir.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/codegen/src/ir.rs b/crates/codegen/src/ir.rs index 4306476303c..47e42ef3b3f 100644 --- a/crates/codegen/src/ir.rs +++ b/crates/codegen/src/ir.rs @@ -1685,10 +1685,7 @@ fn duplicate_end_returns(blocks: &mut [Block]) { current = BlockIdx(0); while current != BlockIdx::NULL { let block = &blocks[current.idx()]; - if current != last_block - && block.next == last_block - && !block.cold - && !block.except_handler + if current != last_block && block.next == last_block && !block.cold && !block.except_handler { let has_fallthrough = block .instructions From 1092110e43e91d08826866b701193ed825d5ffcf Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Fri, 20 Mar 2026 23:39:43 +0900 Subject: [PATCH 09/34] Remove expectedFailure from test_intrinsic_1 in test_dis --- Lib/test/test_dis.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index 5480ea9b8c3..15455a0fce8 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -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) From cc23051c54213d704cd4b51f92f62214dc586638 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Fri, 20 Mar 2026 23:46:26 +0900 Subject: [PATCH 10/34] Emit TO_BOOL before conditional jumps for all expressions including Compare --- crates/codegen/src/compile.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 53d092ac6df..47b2a372929 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -6771,10 +6771,7 @@ 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); - } + emit!(self, Instruction::ToBool); if condition { emit!( self, From 643861b3fc6fa250fe141887d1bc3987577b9309 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Sat, 21 Mar 2026 10:07:34 +0900 Subject: [PATCH 11/34] Add __classdict__ cell for classes with function definitions Set needs_classdict=true for class scopes that contain function definitions (def/async def), matching CPython 3.14's behavior for PEP 649 deferred annotation support. Also restore the Compare expression check in compile_jump_if to skip TO_BOOL for comparison operations. --- crates/codegen/src/compile.rs | 5 ++++- crates/codegen/src/symboltable.rs | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 47b2a372929..53d092ac6df 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -6771,7 +6771,10 @@ impl Compiler { _ => { // Fall back case which always will work! self.compile_expression(expression)?; - emit!(self, Instruction::ToBool); + // Compare already produces a bool; everything else needs TO_BOOL + if !matches!(expression, ast::Expr::Compare(_)) { + emit!(self, Instruction::ToBool); + } if condition { emit!( self, diff --git a/crates/codegen/src/symboltable.rs b/crates/codegen/src/symboltable.rs index b7ef3e7eaba..afa51ef256b 100644 --- a/crates/codegen/src/symboltable.rs +++ b/crates/codegen/src/symboltable.rs @@ -292,6 +292,20 @@ fn drop_class_free(symbol_table: &mut SymbolTable, newfree: &mut IndexSet Date: Sat, 21 Mar 2026 10:45:55 +0900 Subject: [PATCH 12/34] Emit __classdictcell__ store in class body epilogue Store the __classdict__ cell reference as __classdictcell__ in the class namespace when the class has __classdict__ as a cell variable. Uses LOAD_DEREF (RustPython separates cell vars from fast locals unlike CPython's unified array). --- crates/codegen/src/compile.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 53d092ac6df..62d9d413a60 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -4690,6 +4690,19 @@ impl Compiler { ); } + // Store __classdictcell__ if __classdict__ is a cell variable + if self.current_symbol_table().needs_classdict { + let classdict_idx = self.get_cell_var_index("__classdict__")?; + emit!(self, Instruction::LoadDeref { i: classdict_idx }); + let classdictcell = self.name("__classdictcell__"); + emit!( + self, + Instruction::StoreName { + namei: classdictcell + } + ); + } + if let Some(classcell_idx) = classcell_idx { emit!( self, From 50eaae4bda37ebe6436e261aa900bc49b2f79ce6 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Sat, 21 Mar 2026 10:58:45 +0900 Subject: [PATCH 13/34] Always run DCE to remove dead code after terminal instructions Run basic dead code elimination (truncating instructions after RETURN_VALUE/RAISE/JUMP within blocks) at all optimization levels, not just optimize > 0. CPython always removes this dead code during assembly. --- crates/codegen/src/ir.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/codegen/src/ir.rs b/crates/codegen/src/ir.rs index 47e42ef3b3f..446b458a750 100644 --- a/crates/codegen/src/ir.rs +++ b/crates/codegen/src/ir.rs @@ -195,8 +195,9 @@ impl CodeInfo { self.remove_unused_consts(); self.remove_nops(); + // DCE always runs (removes dead code after terminal instructions) + self.dce(); if opts.optimize > 0 { - self.dce(); self.peephole_optimize(); } From fdcccad5d8794cdbd23ad0bcdc17ee2a2c1abeac Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Sat, 21 Mar 2026 11:08:36 +0900 Subject: [PATCH 14/34] Restrict LOAD_ATTR plain mode to module/class scope imports Only use plain LOAD_ATTR + PUSH_NULL for imports at module or class scope. Function-local imports use method call mode LOAD_ATTR, matching CPython 3.14's behavior. --- crates/codegen/src/compile.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 62d9d413a60..558f60b51a2 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -7564,11 +7564,18 @@ impl Compiler { } else { self.compile_expression(value)?; let idx = self.name(attr.as_str()); - // Imported names (modules) use plain LOAD_ATTR + PUSH_NULL; - // other names use method call mode LOAD_ATTR - let is_import = matches!(value.as_ref(), ast::Expr::Name(ast::ExprName { id, .. }) - if self.current_symbol_table().symbols.get(id.as_str()) - .is_some_and(|s| s.flags.contains(SymbolFlags::IMPORTED))); + // Module-level imported names use plain LOAD_ATTR + PUSH_NULL; + // function-local imports and other names use method call mode + let in_function = matches!( + self.current_symbol_table().typ, + CompilerScope::Function + | CompilerScope::AsyncFunction + | CompilerScope::Lambda + ); + let is_import = !in_function + && matches!(value.as_ref(), ast::Expr::Name(ast::ExprName { id, .. }) + if self.current_symbol_table().symbols.get(id.as_str()) + .is_some_and(|s| s.flags.contains(SymbolFlags::IMPORTED))); if is_import { self.emit_load_attr(idx); emit!(self, Instruction::PushNull); From 83a548eace3bde36b3e9264e01944bdfa0c1bf1c Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Sat, 21 Mar 2026 22:34:05 +0900 Subject: [PATCH 15/34] Eliminate unreachable blocks after jump normalization Split DCE into two phases: (1) within-block truncation after terminal instructions (always runs), (2) whole-block elimination for blocks only reachable via fall-through from terminal blocks (runs after normalize_jumps when dead jump instructions exist). --- crates/codegen/src/ir.rs | 41 +++ ...pile__tests__nested_double_async_with.snap | 257 +++++++++--------- 2 files changed, 163 insertions(+), 135 deletions(-) diff --git a/crates/codegen/src/ir.rs b/crates/codegen/src/ir.rs index 446b458a750..0b433d01039 100644 --- a/crates/codegen/src/ir.rs +++ b/crates/codegen/src/ir.rs @@ -209,6 +209,7 @@ impl CodeInfo { label_exception_targets(&mut self.blocks); push_cold_blocks_to_end(&mut self.blocks); normalize_jumps(&mut self.blocks); + self.eliminate_unreachable_blocks(); duplicate_end_returns(&mut self.blocks); self.optimize_load_global_push_null(); @@ -548,6 +549,7 @@ impl CodeInfo { } fn dce(&mut self) { + // Truncate instructions after terminal instructions within each block for block in &mut self.blocks { let mut last_instr = None; for (i, ins) in block.instructions.iter().enumerate() { @@ -562,6 +564,45 @@ impl CodeInfo { } } + /// Clear blocks that are unreachable (not entry, not a jump target, + /// and only reachable via fall-through from a terminal block). + fn eliminate_unreachable_blocks(&mut self) { + let mut reachable = vec![false; self.blocks.len()]; + reachable[0] = true; + // Mark blocks reachable via jump targets and exception handlers + for block in &self.blocks { + for ins in &block.instructions { + if ins.target != BlockIdx::NULL { + reachable[ins.target.idx()] = true; + } + if let Some(eh) = &ins.except_handler { + reachable[eh.handler_block.idx()] = true; + } + } + } + // Mark blocks reachable via fall-through from non-terminal blocks + let mut current = BlockIdx(0); + while current != BlockIdx::NULL { + let next = self.blocks[current.idx()].next; + if next != BlockIdx::NULL + && !self.blocks[current.idx()] + .instructions + .last() + .is_some_and(|ins| { + ins.instr.is_scope_exit() || ins.instr.is_unconditional_jump() + }) + { + reachable[next.idx()] = true; + } + current = next; + } + for (i, block) in self.blocks.iter_mut().enumerate() { + if !reachable[i] { + block.instructions.clear(); + } + } + } + /// Constant folding: fold LOAD_CONST/LOAD_SMALL_INT + BUILD_TUPLE into LOAD_CONST tuple /// fold_tuple_of_constants fn fold_tuple_constants(&mut self) { diff --git a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap index 7a1db8e7b8c..b8bdeb05105 100644 --- a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap +++ b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap @@ -1,6 +1,6 @@ --- source: crates/codegen/src/compile.rs -assertion_line: 9089 +assertion_line: 9375 expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIteration('spam'), StopAsyncIteration('ham')):\n with self.subTest(type=type(stop_exc)):\n try:\n async with egg():\n raise stop_exc\n except Exception as ex:\n self.assertIs(ex, stop_exc)\n else:\n self.fail(f'{stop_exc} was suppressed')\n\")" --- 1 0 RESUME (0) @@ -115,162 +115,149 @@ expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIter 5 102 CLEANUP_THROW 103 JUMP_BACKWARD_NO_INTERRUPT(10) - - 6 104 NOP - - 5 105 PUSH_NULL - 106 LOAD_CONST (None) - 107 LOAD_CONST (None) - 108 LOAD_CONST (None) - 109 CALL (3) - 110 CACHE - 111 CACHE - 112 CACHE - 113 GET_AWAITABLE (2) - 114 LOAD_CONST (None) - 115 SEND (4) - 116 CACHE - 117 YIELD_VALUE (1) - 118 RESUME (3) - 119 JUMP_BACKWARD_NO_INTERRUPT(5) - 120 CLEANUP_THROW - 121 END_SEND - 122 POP_TOP - 123 JUMP_FORWARD (27) - 124 PUSH_EXC_INFO - 125 WITH_EXCEPT_START - 126 GET_AWAITABLE (2) - 127 LOAD_CONST (None) - 128 SEND (4) + 104 SEND (4) + 105 CACHE + 106 YIELD_VALUE (1) + 107 RESUME (3) + 108 JUMP_BACKWARD_NO_INTERRUPT(5) + 109 CLEANUP_THROW + 110 END_SEND + 111 POP_TOP + 112 JUMP_FORWARD (27) + 113 PUSH_EXC_INFO + 114 WITH_EXCEPT_START + 115 GET_AWAITABLE (2) + 116 LOAD_CONST (None) + 117 SEND (4) + 118 CACHE + 119 YIELD_VALUE (1) + 120 RESUME (3) + 121 JUMP_BACKWARD_NO_INTERRUPT(5) + 122 CLEANUP_THROW + 123 END_SEND + 124 TO_BOOL + 125 CACHE + 126 CACHE + 127 CACHE + 128 POP_JUMP_IF_TRUE (2) 129 CACHE - 130 YIELD_VALUE (1) - 131 RESUME (3) - 132 JUMP_BACKWARD_NO_INTERRUPT(5) - 133 CLEANUP_THROW - 134 END_SEND - 135 TO_BOOL - 136 CACHE - 137 CACHE - 138 CACHE - 139 POP_JUMP_IF_TRUE (2) - 140 CACHE - 141 NOT_TAKEN - 142 RERAISE (2) - 143 POP_TOP - 144 POP_EXCEPT - 145 POP_TOP - 146 POP_TOP - 147 JUMP_FORWARD (3) - 148 COPY (3) - 149 POP_EXCEPT - 150 RERAISE (1) - 151 JUMP_FORWARD (47) - 152 PUSH_EXC_INFO + 130 NOT_TAKEN + 131 RERAISE (2) + 132 POP_TOP + 133 POP_EXCEPT + 134 POP_TOP + 135 POP_TOP + 136 JUMP_FORWARD (3) + 137 COPY (3) + 138 POP_EXCEPT + 139 RERAISE (1) + 140 JUMP_FORWARD (47) + 141 PUSH_EXC_INFO + + 7 142 LOAD_GLOBAL (12, Exception) + 143 CACHE + 144 CACHE + 145 CACHE + 146 CACHE + 147 CHECK_EXC_MATCH + 148 POP_JUMP_IF_FALSE (34) + 149 CACHE + 150 NOT_TAKEN + 151 STORE_FAST (1, ex) - 7 153 LOAD_GLOBAL (12, Exception) + 8 152 LOAD_GLOBAL (4, self) + 153 CACHE 154 CACHE 155 CACHE 156 CACHE - 157 CACHE - 158 CHECK_EXC_MATCH - 159 POP_JUMP_IF_FALSE (34) + 157 LOAD_ATTR (15, assertIs, method=true) + 158 CACHE + 159 CACHE 160 CACHE - 161 NOT_TAKEN - 162 STORE_FAST (1, ex) - - 8 163 LOAD_GLOBAL (4, self) + 161 CACHE + 162 CACHE + 163 CACHE 164 CACHE 165 CACHE 166 CACHE - 167 CACHE - 168 LOAD_ATTR (15, assertIs, method=true) - 169 CACHE + 167 LOAD_FAST (1, ex) + 168 LOAD_FAST (0, stop_exc) + 169 CALL (2) 170 CACHE 171 CACHE 172 CACHE - 173 CACHE - 174 CACHE - 175 CACHE - 176 CACHE - 177 CACHE - 178 LOAD_FAST (1, ex) - 179 LOAD_FAST (0, stop_exc) - 180 CALL (2) - 181 CACHE - 182 CACHE - 183 CACHE - 184 POP_TOP - 185 JUMP_FORWARD (4) - 186 LOAD_CONST (None) - 187 STORE_FAST (1, ex) - 188 DELETE_FAST (1, ex) - 189 RERAISE (1) - 190 POP_EXCEPT - 191 LOAD_CONST (None) - 192 STORE_FAST (1, ex) - 193 DELETE_FAST (1, ex) - 194 JUMP_FORWARD (28) - 195 RERAISE (0) - 196 COPY (3) - 197 POP_EXCEPT - 198 RERAISE (1) + 173 POP_TOP + 174 JUMP_FORWARD (4) + 175 LOAD_CONST (None) + 176 STORE_FAST (1, ex) + 177 DELETE_FAST (1, ex) + 178 RERAISE (1) + 179 POP_EXCEPT + 180 LOAD_CONST (None) + 181 STORE_FAST (1, ex) + 182 DELETE_FAST (1, ex) + 183 JUMP_FORWARD (28) + 184 RERAISE (0) + 185 COPY (3) + 186 POP_EXCEPT + 187 RERAISE (1) - 10 199 LOAD_GLOBAL (4, self) + 10 188 LOAD_GLOBAL (4, self) + 189 CACHE + 190 CACHE + 191 CACHE + 192 CACHE + 193 LOAD_ATTR (17, fail, method=true) + 194 CACHE + 195 CACHE + 196 CACHE + 197 CACHE + 198 CACHE + 199 CACHE 200 CACHE 201 CACHE 202 CACHE - 203 CACHE - 204 LOAD_ATTR (17, fail, method=true) - 205 CACHE - 206 CACHE - 207 CACHE + 203 LOAD_FAST_BORROW (0, stop_exc) + 204 FORMAT_SIMPLE + 205 LOAD_CONST (" was suppressed") + 206 BUILD_STRING (2) + 207 CALL (1) 208 CACHE 209 CACHE 210 CACHE - 211 CACHE - 212 CACHE - 213 CACHE - 214 LOAD_FAST_BORROW (0, stop_exc) - 215 FORMAT_SIMPLE - 216 LOAD_CONST (" was suppressed") - 217 BUILD_STRING (2) - 218 CALL (1) + 211 POP_TOP + 212 NOP + + 3 213 PUSH_NULL + 214 LOAD_CONST (None) + 215 LOAD_CONST (None) + 216 LOAD_CONST (None) + 217 CALL (3) + >> 218 CACHE 219 CACHE 220 CACHE - 221 CACHE - 222 POP_TOP - 223 NOP - - 3 224 PUSH_NULL - 225 LOAD_CONST (None) - 226 LOAD_CONST (None) - 227 LOAD_CONST (None) - 228 CALL (3) - >> 229 CACHE + 221 POP_TOP + 222 JUMP_FORWARD (18) + 223 PUSH_EXC_INFO + 224 WITH_EXCEPT_START + 225 TO_BOOL + 226 CACHE + 227 CACHE + 228 CACHE + 229 POP_JUMP_IF_TRUE (2) 230 CACHE - 231 CACHE - 232 POP_TOP - 233 JUMP_FORWARD (18) - 234 PUSH_EXC_INFO - 235 WITH_EXCEPT_START - 236 TO_BOOL - 237 CACHE - 238 CACHE - 239 CACHE - 240 POP_JUMP_IF_TRUE (2) - 241 CACHE - 242 NOT_TAKEN - 243 RERAISE (2) - 244 POP_TOP - 245 POP_EXCEPT - 246 POP_TOP - 247 POP_TOP - 248 JUMP_FORWARD (3) - 249 COPY (3) - 250 POP_EXCEPT - 251 RERAISE (1) - 252 JUMP_BACKWARD (229) - 253 CACHE + 231 NOT_TAKEN + 232 RERAISE (2) + 233 POP_TOP + 234 POP_EXCEPT + 235 POP_TOP + 236 POP_TOP + 237 JUMP_FORWARD (3) + 238 COPY (3) + 239 POP_EXCEPT + 240 RERAISE (1) + 241 JUMP_BACKWARD (218) + 242 CACHE 2 MAKE_FUNCTION 3 STORE_NAME (0, test) From 6a592dd35478bfcf658c2213cbf918638b545859 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Sat, 21 Mar 2026 22:44:58 +0900 Subject: [PATCH 16/34] Fold BUILD_TUPLE 0 into LOAD_CONST empty tuple Convert BUILD_TUPLE with size 0 to LOAD_CONST () during constant folding, matching CPython's optimization for empty tuple literals. --- crates/codegen/src/ir.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/crates/codegen/src/ir.rs b/crates/codegen/src/ir.rs index 0b433d01039..de0bf2f7006 100644 --- a/crates/codegen/src/ir.rs +++ b/crates/codegen/src/ir.rs @@ -617,7 +617,23 @@ impl CodeInfo { }; let tuple_size = u32::from(instr.arg) as usize; - if tuple_size == 0 || i < tuple_size { + if tuple_size == 0 { + // BUILD_TUPLE 0 → LOAD_CONST () + let (const_idx, _) = self.metadata.consts.insert_full( + ConstantData::Tuple { + elements: Vec::new(), + }, + ); + block.instructions[i].instr = + Instruction::LoadConst { + consti: Arg::marker(), + } + .into(); + block.instructions[i].arg = OpArg::new(const_idx as u32); + i += 1; + continue; + } + if i < tuple_size { i += 1; continue; } From 1d83df2c225fa53b4040636b0877bcf0f3858df8 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Sun, 22 Mar 2026 03:16:13 +0900 Subject: [PATCH 17/34] Handle __classcell__ and __classdictcell__ in type.__new__ - Remove __classcell__ from class dict after setting the cell value - Add __classdictcell__ handling: set cell to class namespace dict, then remove from class dict - Register __classdictcell__ identifier - Use LoadClosure instead of LoadDeref for __classdictcell__ emission - Reorder MakeFunctionFlag bits to match CPython - Run ruff format on scripts --- crates/codegen/src/compile.rs | 8 +++++- crates/compiler-core/src/bytecode/oparg.rs | 12 ++++---- crates/vm/src/builtins/type.rs | 32 ++++++++++++++++------ crates/vm/src/vm/context.rs | 1 + 4 files changed, 37 insertions(+), 16 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 558f60b51a2..41c5460ca7a 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -4693,7 +4693,13 @@ impl Compiler { // Store __classdictcell__ if __classdict__ is a cell variable if self.current_symbol_table().needs_classdict { let classdict_idx = self.get_cell_var_index("__classdict__")?; - emit!(self, Instruction::LoadDeref { i: classdict_idx }); + // Use LoadClosure to get the cell object itself (not the value inside) + emit!( + self, + PseudoInstruction::LoadClosure { + i: classdict_idx + } + ); let classdictcell = self.name("__classdictcell__"); emit!( self, diff --git a/crates/compiler-core/src/bytecode/oparg.rs b/crates/compiler-core/src/bytecode/oparg.rs index 4b274408ada..91d8d69288b 100644 --- a/crates/compiler-core/src/bytecode/oparg.rs +++ b/crates/compiler-core/src/bytecode/oparg.rs @@ -385,13 +385,13 @@ bitflagset::bitflag! { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[repr(u8)] pub enum MakeFunctionFlag { - Closure = 0, - Annotations = 1, - KwOnlyDefaults = 2, - Defaults = 3, - TypeParams = 4, + Defaults = 0, + KwOnlyDefaults = 1, + Annotations = 2, + Closure = 3, /// PEP 649: __annotate__ function closure (instead of __annotations__ dict) - Annotate = 5, + Annotate = 4, + TypeParams = 5, } } diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 0f63e7106df..5f351afca31 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -2133,15 +2133,29 @@ impl Constructor for PyType { } } - if let Some(cell) = typ.attributes.write().get(identifier!(vm, __classcell__)) { - let cell = PyCellRef::try_from_object(vm, cell.clone()).map_err(|_| { - vm.new_type_error(format!( - "__classcell__ must be a nonlocal cell, not {}", - cell.class().name() - )) - })?; - cell.set(Some(typ.clone().into())); - }; + { + let mut attrs = typ.attributes.write(); + if let Some(cell) = attrs.get(identifier!(vm, __classcell__)) { + let cell = PyCellRef::try_from_object(vm, cell.clone()).map_err(|_| { + vm.new_type_error(format!( + "__classcell__ must be a nonlocal cell, not {}", + cell.class().name() + )) + })?; + cell.set(Some(typ.clone().into())); + attrs.shift_remove(identifier!(vm, __classcell__)); + } + if let Some(cell) = attrs.get(identifier!(vm, __classdictcell__)) { + let cell = PyCellRef::try_from_object(vm, cell.clone()).map_err(|_| { + vm.new_type_error(format!( + "__classdictcell__ must be a nonlocal cell, not {}", + cell.class().name() + )) + })?; + cell.set(Some(dict.clone().into())); + attrs.shift_remove(identifier!(vm, __classdictcell__)); + } + } // All *classes* should have a dict. Exceptions are *instances* of // classes that define __slots__ and instances of built-in classes diff --git a/crates/vm/src/vm/context.rs b/crates/vm/src/vm/context.rs index 46500ab3c2a..fedb641c542 100644 --- a/crates/vm/src/vm/context.rs +++ b/crates/vm/src/vm/context.rs @@ -118,6 +118,7 @@ declare_const_name! { __class__, __class_getitem__, __classcell__, + __classdictcell__, __complex__, __contains__, __copy__, From e1bf8d1e490c0dc71cd5fc1c5d46adc289378e2f Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Sun, 22 Mar 2026 03:46:03 +0900 Subject: [PATCH 18/34] Revert __classdict__ cell and __classdictcell__ changes The __classdict__ cell addition (for classes with function defs) and __classdictcell__ store caused cell initialization failures in importlib. These require deeper VM changes to properly support the cell variable lifecycle. Reverted for stability. --- crates/codegen/src/compile.rs | 19 ------------------- crates/codegen/src/symboltable.rs | 14 -------------- crates/compiler-core/src/bytecode/oparg.rs | 12 ++++++------ 3 files changed, 6 insertions(+), 39 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 41c5460ca7a..cbcf93a2d17 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -4690,25 +4690,6 @@ impl Compiler { ); } - // Store __classdictcell__ if __classdict__ is a cell variable - if self.current_symbol_table().needs_classdict { - let classdict_idx = self.get_cell_var_index("__classdict__")?; - // Use LoadClosure to get the cell object itself (not the value inside) - 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, diff --git a/crates/codegen/src/symboltable.rs b/crates/codegen/src/symboltable.rs index afa51ef256b..b7ef3e7eaba 100644 --- a/crates/codegen/src/symboltable.rs +++ b/crates/codegen/src/symboltable.rs @@ -292,20 +292,6 @@ fn drop_class_free(symbol_table: &mut SymbolTable, newfree: &mut IndexSet Date: Sun, 22 Mar 2026 04:25:11 +0900 Subject: [PATCH 19/34] Fix unreachable block elimination with fixpoint reachability Use fixpoint iteration to properly determine block reachability: only mark jump targets of already-reachable blocks, preventing orphaned blocks from falsely marking their targets as reachable. Also add a final DCE pass after assembly NOP removal to catch dead code created by normalize_jumps. --- crates/codegen/src/ir.rs | 71 ++++-- ...pile__tests__nested_double_async_with.snap | 233 +++++++++--------- 2 files changed, 160 insertions(+), 144 deletions(-) diff --git a/crates/codegen/src/ir.rs b/crates/codegen/src/ir.rs index de0bf2f7006..19beb4048b8 100644 --- a/crates/codegen/src/ir.rs +++ b/crates/codegen/src/ir.rs @@ -209,6 +209,7 @@ impl CodeInfo { label_exception_targets(&mut self.blocks); push_cold_blocks_to_end(&mut self.blocks); normalize_jumps(&mut self.blocks); + self.dce(); // re-run within-block DCE after normalize_jumps creates new instructions self.eliminate_unreachable_blocks(); duplicate_end_returns(&mut self.blocks); self.optimize_load_global_push_null(); @@ -334,6 +335,18 @@ impl CodeInfo { blocks[bi].instructions = kept; } + // Final DCE: truncate instructions after terminal ops in linearized blocks. + // This catches dead code created by normalize_jumps after the initial DCE. + for block in blocks.iter_mut() { + if let Some(pos) = block + .instructions + .iter() + .position(|ins| ins.instr.is_scope_exit() || ins.instr.is_unconditional_jump()) + { + block.instructions.truncate(pos + 1); + } + } + // Pre-compute cache_entries for real (non-pseudo) instructions for block in blocks.iter_mut() { for instr in &mut block.instructions { @@ -569,33 +582,45 @@ impl CodeInfo { fn eliminate_unreachable_blocks(&mut self) { let mut reachable = vec![false; self.blocks.len()]; reachable[0] = true; - // Mark blocks reachable via jump targets and exception handlers - for block in &self.blocks { - for ins in &block.instructions { - if ins.target != BlockIdx::NULL { - reachable[ins.target.idx()] = true; + + // Fixpoint: only mark targets of already-reachable blocks + let mut changed = true; + while changed { + changed = false; + for i in 0..self.blocks.len() { + if !reachable[i] { + continue; + } + // Mark jump targets and exception handlers + for ins in &self.blocks[i].instructions { + if ins.target != BlockIdx::NULL && !reachable[ins.target.idx()] { + reachable[ins.target.idx()] = true; + changed = true; + } + if let Some(eh) = &ins.except_handler { + if !reachable[eh.handler_block.idx()] { + reachable[eh.handler_block.idx()] = true; + changed = true; + } + } } - if let Some(eh) = &ins.except_handler { - reachable[eh.handler_block.idx()] = true; + // Mark fall-through + let next = self.blocks[i].next; + if next != BlockIdx::NULL + && !reachable[next.idx()] + && !self.blocks[i] + .instructions + .last() + .is_some_and(|ins| { + ins.instr.is_scope_exit() || ins.instr.is_unconditional_jump() + }) + { + reachable[next.idx()] = true; + changed = true; } } } - // Mark blocks reachable via fall-through from non-terminal blocks - let mut current = BlockIdx(0); - while current != BlockIdx::NULL { - let next = self.blocks[current.idx()].next; - if next != BlockIdx::NULL - && !self.blocks[current.idx()] - .instructions - .last() - .is_some_and(|ins| { - ins.instr.is_scope_exit() || ins.instr.is_unconditional_jump() - }) - { - reachable[next.idx()] = true; - } - current = next; - } + for (i, block) in self.blocks.iter_mut().enumerate() { if !reachable[i] { block.instructions.clear(); diff --git a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap index b8bdeb05105..438b1642926 100644 --- a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap +++ b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap @@ -1,6 +1,6 @@ --- source: crates/codegen/src/compile.rs -assertion_line: 9375 +assertion_line: 9362 expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIteration('spam'), StopAsyncIteration('ham')):\n with self.subTest(type=type(stop_exc)):\n try:\n async with egg():\n raise stop_exc\n except Exception as ex:\n self.assertIs(ex, stop_exc)\n else:\n self.fail(f'{stop_exc} was suppressed')\n\")" --- 1 0 RESUME (0) @@ -32,7 +32,7 @@ expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIter 24 GET_ITER 25 FOR_ITER (71) 26 CACHE - >> 27 STORE_FAST (0, stop_exc) + 27 STORE_FAST (0, stop_exc) 3 >> 28 LOAD_GLOBAL (4, self) 29 CACHE @@ -115,149 +115,140 @@ expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIter 5 102 CLEANUP_THROW 103 JUMP_BACKWARD_NO_INTERRUPT(10) - 104 SEND (4) - 105 CACHE - 106 YIELD_VALUE (1) - 107 RESUME (3) - 108 JUMP_BACKWARD_NO_INTERRUPT(5) - 109 CLEANUP_THROW - 110 END_SEND - 111 POP_TOP - 112 JUMP_FORWARD (27) - 113 PUSH_EXC_INFO - 114 WITH_EXCEPT_START - 115 GET_AWAITABLE (2) - 116 LOAD_CONST (None) - 117 SEND (4) + 104 PUSH_EXC_INFO + 105 WITH_EXCEPT_START + 106 GET_AWAITABLE (2) + 107 LOAD_CONST (None) + 108 SEND (4) + 109 CACHE + 110 YIELD_VALUE (1) + 111 RESUME (3) + 112 JUMP_BACKWARD_NO_INTERRUPT(5) + 113 CLEANUP_THROW + 114 END_SEND + 115 TO_BOOL + 116 CACHE + 117 CACHE 118 CACHE - 119 YIELD_VALUE (1) - 120 RESUME (3) - 121 JUMP_BACKWARD_NO_INTERRUPT(5) - 122 CLEANUP_THROW - 123 END_SEND - 124 TO_BOOL - 125 CACHE - 126 CACHE - 127 CACHE - 128 POP_JUMP_IF_TRUE (2) - 129 CACHE - 130 NOT_TAKEN - 131 RERAISE (2) - 132 POP_TOP - 133 POP_EXCEPT - 134 POP_TOP - 135 POP_TOP - 136 JUMP_FORWARD (3) - 137 COPY (3) - 138 POP_EXCEPT - 139 RERAISE (1) - 140 JUMP_FORWARD (47) - 141 PUSH_EXC_INFO + 119 POP_JUMP_IF_TRUE (2) + 120 CACHE + 121 NOT_TAKEN + 122 RERAISE (2) + 123 POP_TOP + 124 POP_EXCEPT + 125 POP_TOP + 126 POP_TOP + 127 JUMP_FORWARD (3) + 128 COPY (3) + 129 POP_EXCEPT + 130 RERAISE (1) + 131 JUMP_FORWARD (47) + 132 PUSH_EXC_INFO - 7 142 LOAD_GLOBAL (12, Exception) - 143 CACHE + 7 133 LOAD_GLOBAL (12, Exception) + 134 CACHE + 135 CACHE + 136 CACHE + 137 CACHE + 138 CHECK_EXC_MATCH + 139 POP_JUMP_IF_FALSE (34) + 140 CACHE + 141 NOT_TAKEN + 142 STORE_FAST (1, ex) + + 8 143 LOAD_GLOBAL (4, self) 144 CACHE 145 CACHE 146 CACHE - 147 CHECK_EXC_MATCH - 148 POP_JUMP_IF_FALSE (34) + 147 CACHE + 148 LOAD_ATTR (15, assertIs, method=true) 149 CACHE - 150 NOT_TAKEN - 151 STORE_FAST (1, ex) - - 8 152 LOAD_GLOBAL (4, self) + 150 CACHE + 151 CACHE + 152 CACHE 153 CACHE 154 CACHE 155 CACHE 156 CACHE - 157 LOAD_ATTR (15, assertIs, method=true) - 158 CACHE - 159 CACHE - 160 CACHE + 157 CACHE + 158 LOAD_FAST (1, ex) + 159 LOAD_FAST (0, stop_exc) + 160 CALL (2) 161 CACHE 162 CACHE 163 CACHE - 164 CACHE - 165 CACHE - 166 CACHE - 167 LOAD_FAST (1, ex) - 168 LOAD_FAST (0, stop_exc) - 169 CALL (2) - 170 CACHE - 171 CACHE - 172 CACHE - 173 POP_TOP - 174 JUMP_FORWARD (4) - 175 LOAD_CONST (None) - 176 STORE_FAST (1, ex) - 177 DELETE_FAST (1, ex) + 164 POP_TOP + 165 JUMP_FORWARD (4) + 166 LOAD_CONST (None) + 167 STORE_FAST (1, ex) + 168 DELETE_FAST (1, ex) + 169 RERAISE (1) + 170 POP_EXCEPT + 171 LOAD_CONST (None) + 172 STORE_FAST (1, ex) + 173 DELETE_FAST (1, ex) + 174 JUMP_FORWARD (28) + 175 RERAISE (0) + 176 COPY (3) + 177 POP_EXCEPT 178 RERAISE (1) - 179 POP_EXCEPT - 180 LOAD_CONST (None) - 181 STORE_FAST (1, ex) - 182 DELETE_FAST (1, ex) - 183 JUMP_FORWARD (28) - 184 RERAISE (0) - 185 COPY (3) - 186 POP_EXCEPT - 187 RERAISE (1) - 10 188 LOAD_GLOBAL (4, self) + 10 179 LOAD_GLOBAL (4, self) + 180 CACHE + 181 CACHE + 182 CACHE + 183 CACHE + 184 LOAD_ATTR (17, fail, method=true) + 185 CACHE + 186 CACHE + 187 CACHE + 188 CACHE 189 CACHE 190 CACHE 191 CACHE 192 CACHE - 193 LOAD_ATTR (17, fail, method=true) - 194 CACHE - 195 CACHE - 196 CACHE - 197 CACHE - 198 CACHE + 193 CACHE + 194 LOAD_FAST_BORROW (0, stop_exc) + 195 FORMAT_SIMPLE + 196 LOAD_CONST (" was suppressed") + 197 BUILD_STRING (2) + 198 CALL (1) 199 CACHE 200 CACHE 201 CACHE - 202 CACHE - 203 LOAD_FAST_BORROW (0, stop_exc) - 204 FORMAT_SIMPLE - 205 LOAD_CONST (" was suppressed") - 206 BUILD_STRING (2) - 207 CALL (1) - 208 CACHE - 209 CACHE - 210 CACHE - 211 POP_TOP - 212 NOP + 202 POP_TOP + 203 NOP - 3 213 PUSH_NULL - 214 LOAD_CONST (None) - 215 LOAD_CONST (None) - 216 LOAD_CONST (None) - 217 CALL (3) - >> 218 CACHE + 3 204 PUSH_NULL + 205 LOAD_CONST (None) + 206 LOAD_CONST (None) + 207 LOAD_CONST (None) + 208 CALL (3) + >> 209 CACHE + 210 CACHE + 211 CACHE + 212 POP_TOP + 213 JUMP_FORWARD (18) + 214 PUSH_EXC_INFO + 215 WITH_EXCEPT_START + 216 TO_BOOL + 217 CACHE + 218 CACHE 219 CACHE - 220 CACHE - 221 POP_TOP - 222 JUMP_FORWARD (18) - 223 PUSH_EXC_INFO - 224 WITH_EXCEPT_START - 225 TO_BOOL - 226 CACHE - 227 CACHE - 228 CACHE - 229 POP_JUMP_IF_TRUE (2) - 230 CACHE - 231 NOT_TAKEN - 232 RERAISE (2) - 233 POP_TOP - 234 POP_EXCEPT - 235 POP_TOP - 236 POP_TOP - 237 JUMP_FORWARD (3) - 238 COPY (3) - 239 POP_EXCEPT - 240 RERAISE (1) - 241 JUMP_BACKWARD (218) - 242 CACHE + 220 POP_JUMP_IF_TRUE (2) + 221 CACHE + 222 NOT_TAKEN + 223 RERAISE (2) + 224 POP_TOP + 225 POP_EXCEPT + 226 POP_TOP + 227 POP_TOP + 228 JUMP_FORWARD (3) + 229 COPY (3) + 230 POP_EXCEPT + 231 RERAISE (1) + 232 JUMP_BACKWARD (209) + 233 CACHE 2 MAKE_FUNCTION 3 STORE_NAME (0, test) From 318172567843945efa1f4dce53b3a9cf176f9078 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Sun, 22 Mar 2026 11:28:29 +0900 Subject: [PATCH 20/34] Check enclosing scopes for IMPORTED flag in LOAD_ATTR mode When deciding whether to use plain LOAD_ATTR for attribute calls, check if the name is imported in any enclosing scope (not just the current scope). This handles the common pattern where a module is imported at module level but used inside functions. --- crates/codegen/src/compile.rs | 37 +++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index cbcf93a2d17..8d135b9e875 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -692,6 +692,26 @@ 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; + } + if sym.scope == SymbolScope::Local { + return false; + } + } + for table in self.symbol_table_stack.iter().rev().skip(1) { + if let Some(sym) = table.symbols.get(name) { + if sym.flags.contains(SymbolFlags::IMPORTED) { + return true; + } + } + } + false + } + /// 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 { @@ -7551,18 +7571,11 @@ impl Compiler { } else { self.compile_expression(value)?; let idx = self.name(attr.as_str()); - // Module-level imported names use plain LOAD_ATTR + PUSH_NULL; - // function-local imports and other names use method call mode - let in_function = matches!( - self.current_symbol_table().typ, - CompilerScope::Function - | CompilerScope::AsyncFunction - | CompilerScope::Lambda - ); - let is_import = !in_function - && matches!(value.as_ref(), ast::Expr::Name(ast::ExprName { id, .. }) - if self.current_symbol_table().symbols.get(id.as_str()) - .is_some_and(|s| s.flags.contains(SymbolFlags::IMPORTED))); + // 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); From c345999f070635903d2e7ea06e7e7d497eed7517 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Sun, 22 Mar 2026 11:45:38 +0900 Subject: [PATCH 21/34] Add __classdict__ cell for classes with function definitions Set needs_classdict=true when a class scope contains function definitions (def/async def), matching CPython 3.14 which always creates a __classdict__ cell for PEP 649 support in such classes. --- crates/codegen/src/symboltable.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/crates/codegen/src/symboltable.rs b/crates/codegen/src/symboltable.rs index b7ef3e7eaba..afa51ef256b 100644 --- a/crates/codegen/src/symboltable.rs +++ b/crates/codegen/src/symboltable.rs @@ -292,6 +292,20 @@ fn drop_class_free(symbol_table: &mut SymbolTable, newfree: &mut IndexSet Date: Sun, 22 Mar 2026 12:32:15 +0900 Subject: [PATCH 22/34] Store __classdictcell__ in class body epilogue Store the __classdict__ cell reference as __classdictcell__ in the class namespace using LoadClosure (which loads the cell object itself, not the value inside). This matches CPython 3.14's class body epilogue. --- crates/codegen/src/compile.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 8d135b9e875..9c41f9d0065 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -4710,6 +4710,24 @@ impl Compiler { ); } + // Store __classdictcell__ if __classdict__ is a cell variable + if self.current_symbol_table().needs_classdict { + let classdict_idx = self.get_cell_var_index("__classdict__")?; + emit!( + self, + PseudoInstruction::LoadClosure { + i: classdict_idx.as_u32() + } + ); + let classdictcell = self.name("__classdictcell__"); + emit!( + self, + Instruction::StoreName { + namei: classdictcell + } + ); + } + if let Some(classcell_idx) = classcell_idx { emit!( self, From 0793cb2184e001f43bf4404e5d51c85e2c3d2afd Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Sun, 22 Mar 2026 13:19:48 +0900 Subject: [PATCH 23/34] Fix clippy collapsible_if warnings and cargo fmt --- crates/codegen/src/compile.rs | 17 +++++++---------- crates/codegen/src/ir.rs | 36 +++++++++++++++-------------------- 2 files changed, 22 insertions(+), 31 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 9c41f9d0065..c20f786cb25 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -697,19 +697,16 @@ impl Compiler { if let Some(sym) = self.current_symbol_table().symbols.get(name) { if sym.flags.contains(SymbolFlags::IMPORTED) { return true; - } - if sym.scope == SymbolScope::Local { + } else if sym.scope == SymbolScope::Local { return false; } } - for table in self.symbol_table_stack.iter().rev().skip(1) { - if let Some(sym) = table.symbols.get(name) { - if sym.flags.contains(SymbolFlags::IMPORTED) { - return true; - } - } - } - 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. diff --git a/crates/codegen/src/ir.rs b/crates/codegen/src/ir.rs index 19beb4048b8..a9923bd35be 100644 --- a/crates/codegen/src/ir.rs +++ b/crates/codegen/src/ir.rs @@ -597,23 +597,20 @@ impl CodeInfo { reachable[ins.target.idx()] = true; changed = true; } - if let Some(eh) = &ins.except_handler { - if !reachable[eh.handler_block.idx()] { - reachable[eh.handler_block.idx()] = true; - changed = true; - } + if let Some(eh) = &ins.except_handler + && !reachable[eh.handler_block.idx()] + { + reachable[eh.handler_block.idx()] = true; + changed = true; } } // Mark fall-through let next = self.blocks[i].next; if next != BlockIdx::NULL && !reachable[next.idx()] - && !self.blocks[i] - .instructions - .last() - .is_some_and(|ins| { - ins.instr.is_scope_exit() || ins.instr.is_unconditional_jump() - }) + && !self.blocks[i].instructions.last().is_some_and(|ins| { + ins.instr.is_scope_exit() || ins.instr.is_unconditional_jump() + }) { reachable[next.idx()] = true; changed = true; @@ -644,16 +641,13 @@ impl CodeInfo { let tuple_size = u32::from(instr.arg) as usize; if tuple_size == 0 { // BUILD_TUPLE 0 → LOAD_CONST () - let (const_idx, _) = self.metadata.consts.insert_full( - ConstantData::Tuple { - elements: Vec::new(), - }, - ); - block.instructions[i].instr = - Instruction::LoadConst { - consti: Arg::marker(), - } - .into(); + let (const_idx, _) = self.metadata.consts.insert_full(ConstantData::Tuple { + elements: Vec::new(), + }); + block.instructions[i].instr = Instruction::LoadConst { + consti: Arg::marker(), + } + .into(); block.instructions[i].arg = OpArg::new(const_idx as u32); i += 1; continue; From d29b2a95e14cc34e8f6de3aa21cbf3e5f5545e17 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Sun, 22 Mar 2026 18:19:55 +0900 Subject: [PATCH 24/34] Revert __classdict__ and __classdictcell__ changes (cause import failures) --- crates/codegen/src/compile.rs | 18 ------------------ crates/codegen/src/symboltable.rs | 14 -------------- 2 files changed, 32 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index c20f786cb25..843a8edbba0 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -4707,24 +4707,6 @@ impl Compiler { ); } - // Store __classdictcell__ if __classdict__ is a cell variable - if self.current_symbol_table().needs_classdict { - let classdict_idx = self.get_cell_var_index("__classdict__")?; - emit!( - self, - PseudoInstruction::LoadClosure { - i: classdict_idx.as_u32() - } - ); - let classdictcell = self.name("__classdictcell__"); - emit!( - self, - Instruction::StoreName { - namei: classdictcell - } - ); - } - if let Some(classcell_idx) = classcell_idx { emit!( self, diff --git a/crates/codegen/src/symboltable.rs b/crates/codegen/src/symboltable.rs index afa51ef256b..b7ef3e7eaba 100644 --- a/crates/codegen/src/symboltable.rs +++ b/crates/codegen/src/symboltable.rs @@ -292,20 +292,6 @@ fn drop_class_free(symbol_table: &mut SymbolTable, newfree: &mut IndexSet Date: Sun, 22 Mar 2026 18:47:04 +0900 Subject: [PATCH 25/34] Revert type.__new__ __classcell__ removal and __classdictcell__ handling Revert the class cell cleanup changes from e6975f973 that cause import failures when frozen module bytecode is stale. The original behavior (not removing __classcell__ from class dict) is restored. --- crates/vm/src/builtins/type.rs | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 5f351afca31..0f63e7106df 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -2133,29 +2133,15 @@ impl Constructor for PyType { } } - { - let mut attrs = typ.attributes.write(); - if let Some(cell) = attrs.get(identifier!(vm, __classcell__)) { - let cell = PyCellRef::try_from_object(vm, cell.clone()).map_err(|_| { - vm.new_type_error(format!( - "__classcell__ must be a nonlocal cell, not {}", - cell.class().name() - )) - })?; - cell.set(Some(typ.clone().into())); - attrs.shift_remove(identifier!(vm, __classcell__)); - } - if let Some(cell) = attrs.get(identifier!(vm, __classdictcell__)) { - let cell = PyCellRef::try_from_object(vm, cell.clone()).map_err(|_| { - vm.new_type_error(format!( - "__classdictcell__ must be a nonlocal cell, not {}", - cell.class().name() - )) - })?; - cell.set(Some(dict.clone().into())); - attrs.shift_remove(identifier!(vm, __classdictcell__)); - } - } + if let Some(cell) = typ.attributes.write().get(identifier!(vm, __classcell__)) { + let cell = PyCellRef::try_from_object(vm, cell.clone()).map_err(|_| { + vm.new_type_error(format!( + "__classcell__ must be a nonlocal cell, not {}", + cell.class().name() + )) + })?; + cell.set(Some(typ.clone().into())); + }; // All *classes* should have a dict. Exceptions are *instances* of // classes that define __slots__ and instances of built-in classes From c921c8bcc29889580cc980b6a849b8c90e9ecaf1 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Sun, 22 Mar 2026 19:21:54 +0900 Subject: [PATCH 26/34] Re-add __classdict__ cell and __classdictcell__ store Restore the __classdict__ cell for classes with function definitions and __classdictcell__ store in class body epilogue. Previous failure was caused by stale .pyc cache files containing bytecode from an intermediate MakeFunctionFlag reorder attempt, not by these changes themselves. --- crates/codegen/src/compile.rs | 18 ++++++++++++++++++ crates/codegen/src/symboltable.rs | 13 +++++++++++++ 2 files changed, 31 insertions(+) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 843a8edbba0..879eb1fc0af 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -4707,6 +4707,24 @@ impl Compiler { ); } + // 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, diff --git a/crates/codegen/src/symboltable.rs b/crates/codegen/src/symboltable.rs index b7ef3e7eaba..c6384d5f167 100644 --- a/crates/codegen/src/symboltable.rs +++ b/crates/codegen/src/symboltable.rs @@ -292,6 +292,19 @@ fn drop_class_free(symbol_table: &mut SymbolTable, newfree: &mut IndexSet Date: Sun, 22 Mar 2026 20:07:22 +0900 Subject: [PATCH 27/34] Reorder MakeFunctionFlag to match CPython's SET_FUNCTION_ATTRIBUTE Reorder discriminants: Defaults=0, KwOnlyDefaults=1, Annotations=2, Closure=3, Annotate=4, TypeParams=5. This aligns the oparg values with CPython 3.14's convention. Note: after this change, stale .pyc cache files must be deleted (find . -name '*.pyc' -delete) to avoid bytecode mismatch errors. --- crates/compiler-core/src/bytecode/oparg.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/compiler-core/src/bytecode/oparg.rs b/crates/compiler-core/src/bytecode/oparg.rs index 4b274408ada..91d8d69288b 100644 --- a/crates/compiler-core/src/bytecode/oparg.rs +++ b/crates/compiler-core/src/bytecode/oparg.rs @@ -385,13 +385,13 @@ bitflagset::bitflag! { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[repr(u8)] pub enum MakeFunctionFlag { - Closure = 0, - Annotations = 1, - KwOnlyDefaults = 2, - Defaults = 3, - TypeParams = 4, + Defaults = 0, + KwOnlyDefaults = 1, + Annotations = 2, + Closure = 3, /// PEP 649: __annotate__ function closure (instead of __annotations__ dict) - Annotate = 5, + Annotate = 4, + TypeParams = 5, } } From 560d8ae9f126ea860f6941c24d86e3c698af12f8 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Sun, 22 Mar 2026 20:28:21 +0900 Subject: [PATCH 28/34] Use CPython-compatible power-of-two encoding for SET_FUNCTION_ATTRIBUTE Override From/TryFrom for MakeFunctionFlag to use power-of-two values (1,2,4,8,16,32) matching CPython's SET_FUNCTION_ATTRIBUTE oparg encoding, instead of sequential discriminants (0,1,2,3,4,5). --- crates/compiler-core/src/bytecode/oparg.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/crates/compiler-core/src/bytecode/oparg.rs b/crates/compiler-core/src/bytecode/oparg.rs index 91d8d69288b..11f1a59c38b 100644 --- a/crates/compiler-core/src/bytecode/oparg.rs +++ b/crates/compiler-core/src/bytecode/oparg.rs @@ -403,14 +403,24 @@ bitflagset::bitflagset! { impl TryFrom for MakeFunctionFlag { type Error = MarshalError; + /// Decode from CPython-compatible power-of-two value fn try_from(value: u32) -> Result { - Self::try_from(value as u8).map_err(|_| MarshalError::InvalidBytecode) + match value { + 0x01 => Ok(Self::Defaults), + 0x02 => Ok(Self::KwOnlyDefaults), + 0x04 => Ok(Self::Annotations), + 0x08 => Ok(Self::Closure), + 0x10 => Ok(Self::Annotate), + 0x20 => Ok(Self::TypeParams), + _ => Err(MarshalError::InvalidBytecode), + } } } impl From for u32 { + /// Encode as CPython-compatible power-of-two value fn from(flag: MakeFunctionFlag) -> Self { - flag as u32 + 1u32 << (flag as u32) } } From 16ce7fb5727bd6291acd7e1701c3b0537e370973 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Sun, 22 Mar 2026 21:53:10 +0900 Subject: [PATCH 29/34] Remove expectedFailure from test_elim_jump_after_return1 and test_no_jump_over_return_out_of_finally_block --- Lib/test/test_peepholer.py | 1 - Lib/test/test_sys_settrace.py | 2 -- 2 files changed, 3 deletions(-) diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py index d9cec936e47..d3a3c159b81 100644 --- a/Lib/test/test_peepholer.py +++ b/Lib/test/test_peepholer.py @@ -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): diff --git a/Lib/test/test_sys_settrace.py b/Lib/test/test_sys_settrace.py index a98b4d22760..d3232436f74 100644 --- a/Lib/test/test_sys_settrace.py +++ b/Lib/test/test_sys_settrace.py @@ -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: From 98209939ba00e94f23616e4492813485106d55a1 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Sun, 22 Mar 2026 21:57:11 +0900 Subject: [PATCH 30/34] Remove __classcell__ and __classdictcell__ from class dict in type.__new__ --- crates/vm/src/builtins/type.rs | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 0f63e7106df..5f351afca31 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -2133,15 +2133,29 @@ impl Constructor for PyType { } } - if let Some(cell) = typ.attributes.write().get(identifier!(vm, __classcell__)) { - let cell = PyCellRef::try_from_object(vm, cell.clone()).map_err(|_| { - vm.new_type_error(format!( - "__classcell__ must be a nonlocal cell, not {}", - cell.class().name() - )) - })?; - cell.set(Some(typ.clone().into())); - }; + { + let mut attrs = typ.attributes.write(); + if let Some(cell) = attrs.get(identifier!(vm, __classcell__)) { + let cell = PyCellRef::try_from_object(vm, cell.clone()).map_err(|_| { + vm.new_type_error(format!( + "__classcell__ must be a nonlocal cell, not {}", + cell.class().name() + )) + })?; + cell.set(Some(typ.clone().into())); + attrs.shift_remove(identifier!(vm, __classcell__)); + } + if let Some(cell) = attrs.get(identifier!(vm, __classdictcell__)) { + let cell = PyCellRef::try_from_object(vm, cell.clone()).map_err(|_| { + vm.new_type_error(format!( + "__classdictcell__ must be a nonlocal cell, not {}", + cell.class().name() + )) + })?; + cell.set(Some(dict.clone().into())); + attrs.shift_remove(identifier!(vm, __classdictcell__)); + } + } // All *classes* should have a dict. Exceptions are *instances* of // classes that define __slots__ and instances of built-in classes From 35a8f7e04ea2a828804077a63d6bbe58441faf6d Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Sun, 22 Mar 2026 22:22:24 +0900 Subject: [PATCH 31/34] Remove expectedFailure from test___classcell___expected_behaviour, cargo fmt --- Lib/test/test_super.py | 1 - crates/codegen/src/compile.rs | 7 +------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/Lib/test/test_super.py b/Lib/test/test_super.py index 5548f4c71a2..ac9d2e0906d 100644 --- a/Lib/test/test_super.py +++ b/Lib/test/test_super.py @@ -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): diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 879eb1fc0af..a4be3fe756c 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -4710,12 +4710,7 @@ impl Compiler { // 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 - } - ); + emit!(self, PseudoInstruction::LoadClosure { i: classdict_idx }); let classdictcell = self.name("__classdictcell__"); emit!( self, From e0c7326e535a47fd96741893969f9d2ccadf1ced Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Mon, 23 Mar 2026 01:38:39 +0900 Subject: [PATCH 32/34] Handle MakeCell and CopyFreeVars as no-ops in JIT These prologue instructions are handled at frame creation time by the VM. The JIT operates on already-initialized frames, so these can be safely skipped during compilation. --- crates/jit/src/instructions.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/jit/src/instructions.rs b/crates/jit/src/instructions.rs index 922f665f350..bc5c19c7d2b 100644 --- a/crates/jit/src/instructions.rs +++ b/crates/jit/src/instructions.rs @@ -624,7 +624,10 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { _ => Err(JitCompileError::NotSupported), } } - Instruction::ExtendedArg | Instruction::Cache => Ok(()), + Instruction::ExtendedArg + | Instruction::Cache + | Instruction::MakeCell { .. } + | Instruction::CopyFreeVars { .. } => Ok(()), Instruction::JumpBackward { .. } | Instruction::JumpBackwardNoInterrupt { .. } From c95873974f7b74b3930d39264d1ae2fea0634f9c Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Mon, 23 Mar 2026 09:05:16 +0900 Subject: [PATCH 33/34] Remove expectedFailure from test_load_fast_known_simple --- Lib/test/test_peepholer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py index d3a3c159b81..6bb4249b1ce 100644 --- a/Lib/test/test_peepholer.py +++ b/Lib/test/test_peepholer.py @@ -862,7 +862,6 @@ def setUp(self): self.addCleanup(sys.settrace, sys.gettrace()) sys.settrace(None) - @unittest.expectedFailure # TODO: RUSTPYTHON; BINARY_OP 0 (+) def test_load_fast_known_simple(self): def f(): x = 1 From 926a984eb03a5272c89675bc381cf28c9bb5d0f5 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Mon, 23 Mar 2026 09:31:21 +0900 Subject: [PATCH 34/34] Restore expectedFailure for test_load_fast_known_simple The test expects LOAD_FAST_BORROW_LOAD_FAST_BORROW superinstruction which RustPython does not emit yet. --- Lib/test/test_peepholer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py index 6bb4249b1ce..847ef624d62 100644 --- a/Lib/test/test_peepholer.py +++ b/Lib/test/test_peepholer.py @@ -862,6 +862,7 @@ def setUp(self): self.addCleanup(sys.settrace, sys.gettrace()) sys.settrace(None) + @unittest.expectedFailure # TODO: RUSTPYTHON; no LOAD_FAST_BORROW_LOAD_FAST_BORROW superinstruction def test_load_fast_known_simple(self): def f(): x = 1