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): 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) diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py index d9cec936e47..847ef624d62 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): @@ -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 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/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: diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 3cf9fb7fd6f..a4be3fe756c 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 { @@ -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 { @@ -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 { @@ -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) @@ -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 @@ -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, @@ -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 @@ -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, @@ -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, @@ -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), }); @@ -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 @@ -4661,6 +4682,44 @@ 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 + } + ); + } + + // 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, @@ -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 }); } } } @@ -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!( @@ -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); } } @@ -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); + } if condition { emit!( self, @@ -7240,7 +7303,7 @@ impl Compiler { emit!( compiler, Instruction::ListAppend { - i: generators.len().to_u32(), + i: (generators.len() + 1).to_u32(), } ); Ok(()) @@ -7266,7 +7329,7 @@ impl Compiler { emit!( compiler, Instruction::SetAdd { - i: generators.len().to_u32(), + i: (generators.len() + 1).to_u32(), } ); Ok(()) @@ -7298,7 +7361,7 @@ impl Compiler { emit!( compiler, Instruction::MapAdd { - i: generators.len().to_u32(), + i: (generators.len() + 1).to_u32(), } ); @@ -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 { @@ -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 }); } } diff --git a/crates/codegen/src/ir.rs b/crates/codegen/src/ir.rs index 6be851e15b5..a9923bd35be 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(); } @@ -208,6 +209,9 @@ 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(); let max_stackdepth = self.max_stackdepth()?; @@ -331,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 { @@ -546,6 +562,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() { @@ -560,6 +577,54 @@ 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; + + // 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 + && !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() + }) + { + reachable[next.idx()] = true; + changed = true; + } + } + } + + 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) { @@ -574,7 +639,20 @@ 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; } @@ -1647,6 +1725,65 @@ 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 9dd78c6b7b2..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,21 +1,35 @@ --- source: crates/codegen/src/compile.rs -assertion_line: 9100 +assertion_line: 9317 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) - 3 CACHE - 4 NOT_TAKEN - >> 5 LOAD_CONST (False) - 6 POP_JUMP_IF_FALSE (5) + 1 LOAD_CONST (True) + 2 TO_BOOL + >> 3 CACHE + 4 CACHE + 5 CACHE + 6 POP_JUMP_IF_FALSE (19) 7 CACHE 8 NOT_TAKEN - >> 9 LOAD_CONST (False) - 10 POP_JUMP_IF_FALSE (1) - 11 CACHE - 12 NOT_TAKEN + 9 LOAD_CONST (False) + 10 TO_BOOL + >> 11 CACHE + 12 CACHE + 13 CACHE + 14 POP_JUMP_IF_FALSE (11) + 15 CACHE + 16 NOT_TAKEN + 17 LOAD_CONST (False) + 18 TO_BOOL + >> 19 CACHE + 20 CACHE + 21 CACHE + 22 POP_JUMP_IF_FALSE (3) + 23 CACHE + 24 NOT_TAKEN - 2 13 LOAD_CONST (None) - 14 RETURN_VALUE + 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 e9c3ad8a3c6..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,25 +1,43 @@ --- source: crates/codegen/src/compile.rs -assertion_line: 9110 +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) - 2 POP_JUMP_IF_FALSE (5) - 3 CACHE - 4 NOT_TAKEN - >> 5 LOAD_CONST (False) - 6 POP_JUMP_IF_TRUE (9) + 1 LOAD_CONST (True) + 2 TO_BOOL + >> 3 CACHE + 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) - 11 CACHE - 12 NOT_TAKEN - 13 LOAD_CONST (True) - 14 POP_JUMP_IF_FALSE (1) + 10 TO_BOOL + >> 11 CACHE + 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 (11) + 23 CACHE + 24 NOT_TAKEN + 25 LOAD_CONST (True) + 26 TO_BOOL + 27 CACHE + 28 CACHE + 29 CACHE + 30 POP_JUMP_IF_FALSE (3) + 31 CACHE + 32 NOT_TAKEN - 2 17 LOAD_CONST (None) - 18 RETURN_VALUE + 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 83212144b99..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,21 +1,35 @@ --- source: crates/codegen/src/compile.rs -assertion_line: 9090 +assertion_line: 9307 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) - 3 CACHE - 4 NOT_TAKEN - >> 5 LOAD_CONST (False) - 6 POP_JUMP_IF_TRUE (5) + 1 LOAD_CONST (True) + 2 TO_BOOL + >> 3 CACHE + 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 (3) + 23 CACHE + 24 NOT_TAKEN - 2 13 LOAD_CONST (None) - 14 RETURN_VALUE + 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__nested_double_async_with.snap b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap index 7a1db8e7b8c..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: 9089 +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,162 +115,140 @@ 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) + 104 PUSH_EXC_INFO + 105 WITH_EXCEPT_START + 106 GET_AWAITABLE (2) 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) + 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 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) - 129 CACHE - 130 YIELD_VALUE (1) - 131 RESUME (3) - 132 JUMP_BACKWARD_NO_INTERRUPT(5) - 133 CLEANUP_THROW - 134 END_SEND - 135 TO_BOOL + 117 CACHE + 118 CACHE + 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 133 LOAD_GLOBAL (12, Exception) + 134 CACHE + 135 CACHE 136 CACHE 137 CACHE - 138 CACHE - 139 POP_JUMP_IF_TRUE (2) + 138 CHECK_EXC_MATCH + 139 POP_JUMP_IF_FALSE (34) 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 + 142 STORE_FAST (1, ex) - 7 153 LOAD_GLOBAL (12, Exception) + 8 143 LOAD_GLOBAL (4, self) + 144 CACHE + 145 CACHE + 146 CACHE + 147 CACHE + 148 LOAD_ATTR (15, assertIs, method=true) + 149 CACHE + 150 CACHE + 151 CACHE + 152 CACHE + 153 CACHE 154 CACHE 155 CACHE 156 CACHE 157 CACHE - 158 CHECK_EXC_MATCH - 159 POP_JUMP_IF_FALSE (34) - 160 CACHE - 161 NOT_TAKEN - 162 STORE_FAST (1, ex) + 158 LOAD_FAST (1, ex) + 159 LOAD_FAST (0, stop_exc) + 160 CALL (2) + 161 CACHE + 162 CACHE + 163 CACHE + 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) - 8 163 LOAD_GLOBAL (4, self) - 164 CACHE - 165 CACHE - 166 CACHE - 167 CACHE - 168 LOAD_ATTR (15, assertIs, method=true) - 169 CACHE - 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) + 10 179 LOAD_GLOBAL (4, self) + 180 CACHE 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) - - 10 199 LOAD_GLOBAL (4, self) + 184 LOAD_ATTR (17, fail, method=true) + 185 CACHE + 186 CACHE + 187 CACHE + 188 CACHE + 189 CACHE + 190 CACHE + 191 CACHE + 192 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 CACHE - 204 LOAD_ATTR (17, fail, method=true) - 205 CACHE - 206 CACHE - 207 CACHE - 208 CACHE - 209 CACHE + 202 POP_TOP + 203 NOP + + 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 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) + 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 + 220 POP_JUMP_IF_TRUE (2) 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 - 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 + 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) 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 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) } } 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 { .. } 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/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() 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__,