diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index b469c8ab2de..0495c58329c 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -2486,7 +2486,6 @@ def f(): class TestStaticAttributes(unittest.TestCase): - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: type object 'C' has no attribute '__static_attributes__' def test_basic(self): class C: def f(self): @@ -2518,7 +2517,6 @@ def h(self, a): self.assertEqual(sorted(C.__static_attributes__), ['u', 'v', 'x', 'y', 'z']) - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: type object 'C' has no attribute '__static_attributes__' def test_nested_class(self): class C: def f(self): @@ -2533,7 +2531,6 @@ def g(self): self.assertEqual(sorted(C.__static_attributes__), ['x', 'y']) self.assertEqual(sorted(C.D.__static_attributes__), ['y', 'z']) - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: type object 'C' has no attribute '__static_attributes__' def test_subclass(self): class C: def f(self): @@ -2593,7 +2590,6 @@ def test_tuple(self): def test_set(self): self.check_stack_size("{" + "x, " * self.N + "x}") - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 202 not less than or equal to 7 def test_dict(self): self.check_stack_size("{" + "x:x, " * self.N + "x:x}") diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py index 847ef624d62..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; 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_sys_settrace.py b/Lib/test/test_sys_settrace.py index d3232436f74..f9449c7079a 100644 --- a/Lib/test/test_sys_settrace.py +++ b/Lib/test/test_sys_settrace.py @@ -1420,8 +1420,6 @@ def test_jump_out_of_block_backwards(output): output.append(6) output.append(7) - # TODO: RUSTPYTHON - @unittest.expectedFailure @async_jump_test(4, 5, [3, 5]) async def test_jump_out_of_async_for_block_forwards(output): for i in [1]: @@ -1430,8 +1428,6 @@ async def test_jump_out_of_async_for_block_forwards(output): output.append(4) output.append(5) - # TODO: RUSTPYTHON - @unittest.expectedFailure @async_jump_test(5, 2, [2, 4, 2, 4, 5, 6]) async def test_jump_out_of_async_for_block_backwards(output): for i in [1]: @@ -1539,8 +1535,6 @@ def test_jump_forwards_out_of_with_block(output): output.append(2) output.append(3) - # TODO: RUSTPYTHON - @unittest.expectedFailure @async_jump_test(2, 3, [1, 3]) async def test_jump_forwards_out_of_async_with_block(output): async with asynctracecontext(output, 1): @@ -1553,8 +1547,6 @@ def test_jump_backwards_out_of_with_block(output): with tracecontext(output, 2): output.append(3) - # TODO: RUSTPYTHON - @unittest.expectedFailure @async_jump_test(3, 1, [1, 2, 1, 2, 3, -2]) async def test_jump_backwards_out_of_async_with_block(output): output.append(1) @@ -1624,8 +1616,6 @@ def test_jump_across_with(output): with tracecontext(output, 4): output.append(5) - # TODO: RUSTPYTHON - @unittest.expectedFailure @async_jump_test(2, 4, [1, 4, 5, -4]) async def test_jump_across_async_with(output): output.append(1) @@ -1643,8 +1633,6 @@ def test_jump_out_of_with_block_within_for_block(output): output.append(5) output.append(6) - # TODO: RUSTPYTHON - @unittest.expectedFailure @async_jump_test(4, 5, [1, 3, 5, 6]) async def test_jump_out_of_async_with_block_within_for_block(output): output.append(1) @@ -1663,8 +1651,6 @@ def test_jump_out_of_with_block_within_with_block(output): output.append(5) output.append(6) - # TODO: RUSTPYTHON - @unittest.expectedFailure @async_jump_test(4, 5, [1, 2, 3, 5, -2, 6]) async def test_jump_out_of_async_with_block_within_with_block(output): output.append(1) @@ -1684,8 +1670,6 @@ def test_jump_out_of_with_block_within_finally_block(output): output.append(6) output.append(7) - # TODO: RUSTPYTHON - @unittest.expectedFailure @async_jump_test(5, 6, [2, 4, 6, 7]) async def test_jump_out_of_async_with_block_within_finally_block(output): try: @@ -1719,8 +1703,6 @@ def test_jump_out_of_with_assignment(output): output.append(4) output.append(5) - # TODO: RUSTPYTHON - @unittest.expectedFailure @async_jump_test(3, 5, [1, 2, 5]) async def test_jump_out_of_async_with_assignment(output): output.append(1) @@ -1768,8 +1750,6 @@ def test_jump_over_for_block_before_else(output): output.append(7) output.append(8) - # TODO: RUSTPYTHON - @unittest.expectedFailure @async_jump_test(1, 7, [7, 8]) async def test_jump_over_async_for_block_before_else(output): output.append(1) @@ -2053,8 +2033,6 @@ def test_jump_between_with_blocks(output): with tracecontext(output, 4): output.append(5) - # TODO: RUSTPYTHON - @unittest.expectedFailure @async_jump_test(3, 5, [1, 2, 5, -2]) async def test_jump_between_async_with_blocks(output): output.append(1) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index a4be3fe756c..dba8780c902 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -1200,8 +1200,10 @@ impl Compiler { /// Emit RESUME instruction with proper handling for async preamble and module lineno. /// codegen_enter_scope equivalent for RESUME emission. fn emit_resume_for_scope(&mut self, scope_type: CompilerScope, lineno: u32) { - // For async functions/coroutines, emit RETURN_GENERATOR + POP_TOP before RESUME - if scope_type == CompilerScope::AsyncFunction { + // For generators and async functions, emit RETURN_GENERATOR + POP_TOP before RESUME + let is_gen = + scope_type == CompilerScope::AsyncFunction || self.current_symbol_table().is_generator; + if is_gen { emit!(self, Instruction::ReturnGenerator); emit!(self, Instruction::PopTop); } @@ -2758,18 +2760,15 @@ impl Compiler { fn prepare_decorators(&mut self, decorator_list: &[ast::Decorator]) -> CompileResult<()> { for decorator in decorator_list { self.compile_expression(&decorator.expression)?; - emit!(self, Instruction::PushNull); } Ok(()) } - /// Apply decorators in reverse order (LIFO from stack). - /// Stack [dec1, NULL, dec2, NULL, func] -> dec2(func) -> dec1(dec2(func)) - /// The forward loop works because each Call pops from TOS, naturally - /// applying decorators bottom-up (innermost first). + /// Apply decorators: each decorator calls the function below it. + /// Stack: [dec1, dec2, func] → CALL 0 → [dec1, dec2(func)] → CALL 0 → [dec1(dec2(func))] fn apply_decorators(&mut self, decorator_list: &[ast::Decorator]) { for _ in decorator_list { - emit!(self, Instruction::Call { argc: 1 }); + emit!(self, Instruction::Call { argc: 0 }); } } @@ -4510,6 +4509,93 @@ impl Compiler { Ok(()) } + /// Collect attribute names assigned via `self.xxx = ...` in methods. + /// These are stored as __static_attributes__ in the class dict. + fn collect_static_attributes(body: &[ast::Stmt], attrs: Option<&mut IndexSet>) { + let Some(attrs) = attrs else { return }; + for stmt in body { + // Only scan def/async def at class body level + let (params, func_body) = match stmt { + ast::Stmt::FunctionDef(f) => (&f.parameters, &f.body), + _ => continue, + }; + // Get first parameter name (usually "self" or "cls") + let first_param = params + .args + .first() + .or(params.posonlyargs.first()) + .map(|p| &p.parameter.name); + let Some(self_name) = first_param else { + continue; + }; + // Scan function body for self.xxx = ... (STORE_ATTR on first param) + Self::scan_store_attrs(func_body, self_name.as_str(), attrs); + } + } + + /// Recursively scan statements for `name.attr = value` patterns. + fn scan_store_attrs(stmts: &[ast::Stmt], name: &str, attrs: &mut IndexSet) { + for stmt in stmts { + match stmt { + ast::Stmt::Assign(a) => { + for target in &a.targets { + if let ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = target + && let ast::Expr::Name(n) = value.as_ref() + && n.id.as_str() == name + { + attrs.insert(attr.to_string()); + } + } + } + ast::Stmt::AnnAssign(a) => { + if let ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = + a.target.as_ref() + && let ast::Expr::Name(n) = value.as_ref() + && n.id.as_str() == name + { + attrs.insert(attr.to_string()); + } + } + ast::Stmt::AugAssign(a) => { + if let ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = + a.target.as_ref() + && let ast::Expr::Name(n) = value.as_ref() + && n.id.as_str() == name + { + attrs.insert(attr.to_string()); + } + } + ast::Stmt::If(s) => { + Self::scan_store_attrs(&s.body, name, attrs); + for clause in &s.elif_else_clauses { + Self::scan_store_attrs(&clause.body, name, attrs); + } + } + ast::Stmt::For(s) => { + Self::scan_store_attrs(&s.body, name, attrs); + Self::scan_store_attrs(&s.orelse, name, attrs); + } + ast::Stmt::While(s) => { + Self::scan_store_attrs(&s.body, name, attrs); + Self::scan_store_attrs(&s.orelse, name, attrs); + } + ast::Stmt::Try(s) => { + Self::scan_store_attrs(&s.body, name, attrs); + for handler in &s.handlers { + let ast::ExceptHandler::ExceptHandler(h) = handler; + Self::scan_store_attrs(&h.body, name, attrs); + } + Self::scan_store_attrs(&s.orelse, name, attrs); + Self::scan_store_attrs(&s.finalbody, name, attrs); + } + ast::Stmt::With(s) => { + Self::scan_store_attrs(&s.body, name, attrs); + } + _ => {} + } + } + } + // Python/compile.c find_ann fn find_ann(body: &[ast::Stmt]) -> bool { for statement in body { @@ -4617,6 +4703,13 @@ impl Compiler { } ); + // PEP 649: Initialize __classdict__ cell (before __doc__) + if self.current_symbol_table().needs_classdict { + emit!(self, Instruction::LoadLocals); + let classdict_idx = self.get_cell_var_index("__classdict__")?; + emit!(self, Instruction::StoreDeref { i: classdict_idx }); + } + // Store __doc__ only if there's an explicit docstring if let Some(doc) = doc_str { self.emit_load_const(ConstantData::Str { value: doc.into() }); @@ -4645,13 +4738,6 @@ impl Compiler { ); } - // PEP 649: Initialize __classdict__ cell for class annotation scope - if self.current_symbol_table().needs_classdict { - emit!(self, Instruction::LoadLocals); - let classdict_idx = self.get_cell_var_index("__classdict__")?; - emit!(self, Instruction::StoreDeref { i: classdict_idx }); - } - // Handle class annotations based on future_annotations flag if Self::find_ann(body) { if self.future_annotations { @@ -4669,6 +4755,16 @@ impl Compiler { } } + // Collect __static_attributes__: scan methods for self.xxx = ... patterns + Self::collect_static_attributes( + body, + self.code_stack + .last_mut() + .unwrap() + .static_attributes + .as_mut(), + ); + // 3. Compile the class body self.compile_statements(body)?; @@ -4684,7 +4780,7 @@ impl Compiler { // Emit __static_attributes__ tuple { - let attrs: Vec = self + let mut attrs: Vec = self .code_stack .last() .unwrap() @@ -4692,6 +4788,7 @@ impl Compiler { .as_ref() .map(|s| s.iter().cloned().collect()) .unwrap_or_default(); + attrs.sort(); self.emit_load_const(ConstantData::Tuple { elements: attrs .into_iter() @@ -5091,8 +5188,7 @@ impl Compiler { method: SpecialMethod::AEnter } ); // [bound_aexit, bound_aenter] - // bound_aenter is already bound, call with NULL self_or_null - emit!(self, Instruction::PushNull); // [bound_aexit, bound_aenter, NULL] + emit!(self, Instruction::PushNull); emit!(self, Instruction::Call { argc: 0 }); // [bound_aexit, awaitable] emit!(self, Instruction::GetAwaitable { r#where: 1 }); self.emit_load_const(ConstantData::None); @@ -5112,8 +5208,7 @@ impl Compiler { method: SpecialMethod::Enter } ); // [bound_exit, bound_enter] - // bound_enter is already bound, call with NULL self_or_null - emit!(self, Instruction::PushNull); // [bound_exit, bound_enter, NULL] + emit!(self, Instruction::PushNull); emit!(self, Instruction::Call { argc: 0 }); // [bound_exit, enter_result] } @@ -5168,8 +5263,8 @@ impl Compiler { }); // ===== Normal exit path ===== - // Stack: [..., __exit__] - // Call __exit__(None, None, None) + // Stack: [..., bound_exit] + // Call bound_exit(None, None, None) self.set_source_range(with_range); emit!(self, Instruction::PushNull); self.emit_load_const(ConstantData::None); @@ -6894,17 +6989,28 @@ impl Compiler { let has_unpacking = items.iter().any(|item| item.key.is_none()); if !has_unpacking { - // Simple case: no ** unpacking, build all pairs directly - for item in items { - self.compile_expression(item.key.as_ref().unwrap())?; - self.compile_expression(&item.value)?; - } - emit!( - self, - Instruction::BuildMap { - count: u32::try_from(items.len()).expect("too many dict items"), + // STACK_USE_GUIDELINE: for large dicts (16+ pairs), use + // BUILD_MAP 0 + MAP_ADD to avoid excessive stack usage + let big = items.len() * 2 > 30; // ~15 pairs threshold + if big { + emit!(self, Instruction::BuildMap { count: 0 }); + for item in items { + self.compile_expression(item.key.as_ref().unwrap())?; + self.compile_expression(&item.value)?; + emit!(self, Instruction::MapAdd { i: 1 }); } - ); + } else { + for item in items { + self.compile_expression(item.key.as_ref().unwrap())?; + self.compile_expression(&item.value)?; + } + emit!( + self, + Instruction::BuildMap { + count: u32::try_from(items.len()).expect("too many dict items"), + } + ); + } return Ok(()); } diff --git a/crates/codegen/src/ir.rs b/crates/codegen/src/ir.rs index a9923bd35be..0b7eae105e2 100644 --- a/crates/codegen/src/ir.rs +++ b/crates/codegen/src/ir.rs @@ -189,17 +189,19 @@ impl CodeInfo { mut self, opts: &crate::compile::CompileOpts, ) -> crate::InternalResult { - // Always fold tuple constants + // Constant folding passes + self.fold_unary_negative(); self.fold_tuple_constants(); + self.fold_list_constants(); + self.fold_set_constants(); self.convert_to_load_small_int(); self.remove_unused_consts(); self.remove_nops(); // DCE always runs (removes dead code after terminal instructions) self.dce(); - if opts.optimize > 0 { - self.peephole_optimize(); - } + // Peephole optimizer creates superinstructions matching CPython + self.peephole_optimize(); // Always apply LOAD_FAST_BORROW optimization self.optimize_load_fast_borrow(); @@ -625,6 +627,61 @@ impl CodeInfo { } } + /// Fold LOAD_CONST/LOAD_SMALL_INT + UNARY_NEGATIVE → LOAD_CONST (negative value) + fn fold_unary_negative(&mut self) { + for block in &mut self.blocks { + let mut i = 0; + while i + 1 < block.instructions.len() { + let next = &block.instructions[i + 1]; + let Some(Instruction::UnaryNegative) = next.instr.real() else { + i += 1; + continue; + }; + let curr = &block.instructions[i]; + let value = match curr.instr.real() { + Some(Instruction::LoadConst { .. }) => { + let idx = u32::from(curr.arg) as usize; + match self.metadata.consts.get_index(idx) { + Some(ConstantData::Integer { value }) => { + Some(ConstantData::Integer { value: -value }) + } + Some(ConstantData::Float { value }) => { + Some(ConstantData::Float { value: -value }) + } + _ => None, + } + } + Some(Instruction::LoadSmallInt { .. }) => { + let v = u32::from(curr.arg) as i32; + Some(ConstantData::Integer { + value: BigInt::from(-v), + }) + } + _ => None, + }; + if let Some(neg_const) = value { + let (const_idx, _) = self.metadata.consts.insert_full(neg_const); + // Replace LOAD_CONST/LOAD_SMALL_INT with new LOAD_CONST + let load_location = block.instructions[i].location; + block.instructions[i].instr = Instruction::LoadConst { + consti: Arg::marker(), + } + .into(); + block.instructions[i].arg = OpArg::new(const_idx as u32); + // Replace UNARY_NEGATIVE with NOP, inheriting the LOAD_CONST + // location so that remove_nops can clean it up + block.instructions[i + 1].instr = Instruction::Nop.into(); + block.instructions[i + 1].location = load_location; + block.instructions[i + 1].end_location = block.instructions[i].end_location; + // Skip the NOP, don't re-check + i += 2; + } else { + i += 1; + } + } + } + } + /// Constant folding: fold LOAD_CONST/LOAD_SMALL_INT + BUILD_TUPLE into LOAD_CONST tuple /// fold_tuple_of_constants fn fold_tuple_constants(&mut self) { @@ -723,6 +780,195 @@ impl CodeInfo { } } + /// Fold constant list literals: LOAD_CONST* + BUILD_LIST N → + /// BUILD_LIST 0 + LOAD_CONST (tuple) + LIST_EXTEND 1 + fn fold_list_constants(&mut self) { + for block in &mut self.blocks { + let mut i = 0; + while i < block.instructions.len() { + let instr = &block.instructions[i]; + let Some(Instruction::BuildList { .. }) = instr.instr.real() else { + i += 1; + continue; + }; + + let list_size = u32::from(instr.arg) as usize; + if list_size == 0 || i < list_size { + i += 1; + continue; + } + + let start_idx = i - list_size; + let mut elements = Vec::with_capacity(list_size); + let mut all_const = true; + + for j in start_idx..i { + let load_instr = &block.instructions[j]; + match load_instr.instr.real() { + Some(Instruction::LoadConst { .. }) => { + let const_idx = u32::from(load_instr.arg) as usize; + if let Some(constant) = + self.metadata.consts.get_index(const_idx).cloned() + { + elements.push(constant); + } else { + all_const = false; + break; + } + } + Some(Instruction::LoadSmallInt { .. }) => { + let value = u32::from(load_instr.arg) as i32; + elements.push(ConstantData::Integer { + value: BigInt::from(value), + }); + } + _ => { + all_const = false; + break; + } + } + } + + if !all_const || list_size < 3 { + i += 1; + continue; + } + + let tuple_const = ConstantData::Tuple { elements }; + let (const_idx, _) = self.metadata.consts.insert_full(tuple_const); + + let folded_loc = block.instructions[i].location; + let end_loc = block.instructions[i].end_location; + let eh = block.instructions[i].except_handler; + + // slot[start_idx] → BUILD_LIST 0 + block.instructions[start_idx].instr = Instruction::BuildList { + count: Arg::marker(), + } + .into(); + block.instructions[start_idx].arg = OpArg::new(0); + block.instructions[start_idx].location = folded_loc; + block.instructions[start_idx].end_location = end_loc; + block.instructions[start_idx].except_handler = eh; + + // slot[start_idx+1] → LOAD_CONST (tuple) + block.instructions[start_idx + 1].instr = Instruction::LoadConst { + consti: Arg::marker(), + } + .into(); + block.instructions[start_idx + 1].arg = OpArg::new(const_idx as u32); + block.instructions[start_idx + 1].location = folded_loc; + block.instructions[start_idx + 1].end_location = end_loc; + block.instructions[start_idx + 1].except_handler = eh; + + // NOP the rest + for j in (start_idx + 2)..i { + block.instructions[j].instr = Instruction::Nop.into(); + block.instructions[j].location = folded_loc; + } + + // slot[i] (was BUILD_LIST) → LIST_EXTEND 1 + block.instructions[i].instr = Instruction::ListExtend { i: Arg::marker() }.into(); + block.instructions[i].arg = OpArg::new(1); + + i += 1; + } + } + } + + /// Fold constant set literals: LOAD_CONST* + BUILD_SET N → + /// BUILD_SET 0 + LOAD_CONST (frozenset-as-tuple) + SET_UPDATE 1 + fn fold_set_constants(&mut self) { + for block in &mut self.blocks { + let mut i = 0; + while i < block.instructions.len() { + let instr = &block.instructions[i]; + let Some(Instruction::BuildSet { .. }) = instr.instr.real() else { + i += 1; + continue; + }; + + let set_size = u32::from(instr.arg) as usize; + if set_size < 3 || i < set_size { + i += 1; + continue; + } + + let start_idx = i - set_size; + let mut elements = Vec::with_capacity(set_size); + let mut all_const = true; + + for j in start_idx..i { + let load_instr = &block.instructions[j]; + match load_instr.instr.real() { + Some(Instruction::LoadConst { .. }) => { + let const_idx = u32::from(load_instr.arg) as usize; + if let Some(constant) = + self.metadata.consts.get_index(const_idx).cloned() + { + elements.push(constant); + } else { + all_const = false; + break; + } + } + Some(Instruction::LoadSmallInt { .. }) => { + let value = u32::from(load_instr.arg) as i32; + elements.push(ConstantData::Integer { + value: BigInt::from(value), + }); + } + _ => { + all_const = false; + break; + } + } + } + + if !all_const { + i += 1; + continue; + } + + // Use FrozenSet constant (stored as Tuple for now) + let const_data = ConstantData::Tuple { elements }; + let (const_idx, _) = self.metadata.consts.insert_full(const_data); + + let folded_loc = block.instructions[i].location; + let end_loc = block.instructions[i].end_location; + let eh = block.instructions[i].except_handler; + + block.instructions[start_idx].instr = Instruction::BuildSet { + count: Arg::marker(), + } + .into(); + block.instructions[start_idx].arg = OpArg::new(0); + block.instructions[start_idx].location = folded_loc; + block.instructions[start_idx].end_location = end_loc; + block.instructions[start_idx].except_handler = eh; + + block.instructions[start_idx + 1].instr = Instruction::LoadConst { + consti: Arg::marker(), + } + .into(); + block.instructions[start_idx + 1].arg = OpArg::new(const_idx as u32); + block.instructions[start_idx + 1].location = folded_loc; + block.instructions[start_idx + 1].end_location = end_loc; + block.instructions[start_idx + 1].except_handler = eh; + + for j in (start_idx + 2)..i { + block.instructions[j].instr = Instruction::Nop.into(); + block.instructions[j].location = folded_loc; + } + + block.instructions[i].instr = Instruction::SetUpdate { i: Arg::marker() }.into(); + block.instructions[i].arg = OpArg::new(1); + + i += 1; + } + } + } + /// Peephole optimization: combine consecutive instructions into super-instructions fn peephole_optimize(&mut self) { for block in &mut self.blocks { 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 6eea20c54e9..f043fa790f5 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,35 +1,23 @@ --- source: crates/codegen/src/compile.rs -assertion_line: 9317 +assertion_line: 9458 expression: "compile_exec(\"\\\nif True and False and False:\n pass\n\")" --- 1 0 RESUME (0) 1 LOAD_CONST (True) - 2 TO_BOOL + 2 POP_JUMP_IF_FALSE (11) >> 3 CACHE - 4 CACHE - 5 CACHE - 6 POP_JUMP_IF_FALSE (19) - 7 CACHE + 4 NOT_TAKEN + 5 LOAD_CONST (False) + 6 POP_JUMP_IF_FALSE (7) + >> 7 CACHE 8 NOT_TAKEN 9 LOAD_CONST (False) - 10 TO_BOOL + 10 POP_JUMP_IF_FALSE (3) >> 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 + 12 NOT_TAKEN - 2 25 LOAD_CONST (None) - 26 RETURN_VALUE - 27 LOAD_CONST (None) - 28 RETURN_VALUE + 2 13 LOAD_CONST (None) + 14 RETURN_VALUE + 15 LOAD_CONST (None) + 16 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 b6d5edda048..076ae82fecd 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,43 +1,27 @@ --- source: crates/codegen/src/compile.rs -assertion_line: 9327 +assertion_line: 9468 expression: "compile_exec(\"\\\nif (True and False) or (False and True):\n pass\n\")" --- 1 0 RESUME (0) 1 LOAD_CONST (True) - 2 TO_BOOL + 2 POP_JUMP_IF_FALSE (5) >> 3 CACHE - 4 CACHE - 5 CACHE - 6 POP_JUMP_IF_FALSE (9) - 7 CACHE + 4 NOT_TAKEN + >> 5 LOAD_CONST (False) + 6 POP_JUMP_IF_TRUE (9) + >> 7 CACHE 8 NOT_TAKEN >> 9 LOAD_CONST (False) - 10 TO_BOOL - >> 11 CACHE - 12 CACHE - 13 CACHE - 14 POP_JUMP_IF_TRUE (17) + 10 POP_JUMP_IF_FALSE (7) + 11 CACHE + 12 NOT_TAKEN + 13 LOAD_CONST (True) + 14 POP_JUMP_IF_FALSE (3) 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 33 LOAD_CONST (None) - 34 RETURN_VALUE - 35 LOAD_CONST (None) - 36 RETURN_VALUE + 2 17 LOAD_CONST (None) + 18 RETURN_VALUE + 19 LOAD_CONST (None) + 20 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 52d8f1ac0b3..a7beb7766b2 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,35 +1,23 @@ --- source: crates/codegen/src/compile.rs -assertion_line: 9307 +assertion_line: 9448 expression: "compile_exec(\"\\\nif True or False or False:\n pass\n\")" --- 1 0 RESUME (0) 1 LOAD_CONST (True) - 2 TO_BOOL + 2 POP_JUMP_IF_TRUE (9) >> 3 CACHE - 4 CACHE - 5 CACHE - 6 POP_JUMP_IF_TRUE (17) + 4 NOT_TAKEN + >> 5 LOAD_CONST (False) + 6 POP_JUMP_IF_TRUE (5) 7 CACHE 8 NOT_TAKEN >> 9 LOAD_CONST (False) - 10 TO_BOOL + 10 POP_JUMP_IF_FALSE (3) 11 CACHE - 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 + 12 NOT_TAKEN - 2 25 LOAD_CONST (None) - 26 RETURN_VALUE - 27 LOAD_CONST (None) - 28 RETURN_VALUE + 2 13 LOAD_CONST (None) + 14 RETURN_VALUE + 15 LOAD_CONST (None) + 16 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 438b1642926..54d00d5ddb1 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: 9362 +assertion_line: 9496 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) @@ -39,8 +39,8 @@ expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIter 30 CACHE 31 CACHE 32 CACHE - 33 LOAD_ATTR (7, subTest, method=true) - >> 34 CACHE + >> 33 LOAD_ATTR (7, subTest, method=true) + 34 CACHE 35 CACHE 36 CACHE 37 CACHE @@ -52,8 +52,8 @@ expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIter 43 LOAD_GLOBAL (9, NULL + type) 44 CACHE 45 CACHE - 46 CACHE - >> 47 CACHE + >> 46 CACHE + 47 CACHE 48 LOAD_FAST (0, stop_exc) 49 CALL (1) 50 CACHE @@ -142,7 +142,7 @@ expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIter 128 COPY (3) 129 POP_EXCEPT 130 RERAISE (1) - 131 JUMP_FORWARD (47) + 131 JUMP_FORWARD (46) 132 PUSH_EXC_INFO 7 133 LOAD_GLOBAL (12, Exception) @@ -151,7 +151,7 @@ expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIter 136 CACHE 137 CACHE 138 CHECK_EXC_MATCH - 139 POP_JUMP_IF_FALSE (34) + 139 POP_JUMP_IF_FALSE (33) 140 CACHE 141 NOT_TAKEN 142 STORE_FAST (1, ex) @@ -171,34 +171,34 @@ expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIter 155 CACHE 156 CACHE 157 CACHE - 158 LOAD_FAST (1, ex) - 159 LOAD_FAST (0, stop_exc) - 160 CALL (2) + 158 LOAD_FAST_LOAD_FAST (ex, stop_exc) + 159 CALL (2) + 160 CACHE 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) + 163 POP_TOP + 164 JUMP_FORWARD (4) + 165 LOAD_CONST (None) + 166 STORE_FAST (1, ex) + 167 DELETE_FAST (1, ex) + 168 RERAISE (1) + 169 POP_EXCEPT + 170 LOAD_CONST (None) + 171 STORE_FAST (1, ex) + 172 DELETE_FAST (1, ex) + 173 JUMP_FORWARD (28) + 174 RERAISE (0) + 175 COPY (3) + 176 POP_EXCEPT + 177 RERAISE (1) - 10 179 LOAD_GLOBAL (4, self) + 10 178 LOAD_GLOBAL (4, self) + 179 CACHE 180 CACHE 181 CACHE 182 CACHE - 183 CACHE - 184 LOAD_ATTR (17, fail, method=true) + 183 LOAD_ATTR (17, fail, method=true) + 184 CACHE 185 CACHE 186 CACHE 187 CACHE @@ -207,48 +207,47 @@ expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIter 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) + 193 LOAD_FAST_BORROW (0, stop_exc) + 194 FORMAT_SIMPLE + 195 LOAD_CONST (" was suppressed") + 196 BUILD_STRING (2) + 197 CALL (1) + 198 CACHE 199 CACHE 200 CACHE - 201 CACHE - 202 POP_TOP - 203 NOP + 201 POP_TOP + 202 NOP - 3 204 PUSH_NULL + 3 203 PUSH_NULL + 204 LOAD_CONST (None) 205 LOAD_CONST (None) 206 LOAD_CONST (None) - 207 LOAD_CONST (None) - 208 CALL (3) - >> 209 CACHE + 207 CALL (3) + >> 208 CACHE + 209 CACHE 210 CACHE - 211 CACHE - 212 POP_TOP - 213 JUMP_FORWARD (18) - 214 PUSH_EXC_INFO - 215 WITH_EXCEPT_START - 216 TO_BOOL + 211 POP_TOP + 212 JUMP_FORWARD (18) + 213 PUSH_EXC_INFO + 214 WITH_EXCEPT_START + 215 TO_BOOL + 216 CACHE 217 CACHE 218 CACHE - 219 CACHE - 220 POP_JUMP_IF_TRUE (2) - 221 CACHE - 222 NOT_TAKEN - 223 RERAISE (2) - 224 POP_TOP - 225 POP_EXCEPT + 219 POP_JUMP_IF_TRUE (2) + 220 CACHE + 221 NOT_TAKEN + 222 RERAISE (2) + 223 POP_TOP + 224 POP_EXCEPT + 225 POP_TOP 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 + 227 JUMP_FORWARD (3) + 228 COPY (3) + 229 POP_EXCEPT + 230 RERAISE (1) + 231 JUMP_BACKWARD (208) + 232 CACHE 2 MAKE_FUNCTION 3 STORE_NAME (0, test) diff --git a/crates/codegen/src/symboltable.rs b/crates/codegen/src/symboltable.rs index c6384d5f167..6fd54c551d4 100644 --- a/crates/codegen/src/symboltable.rs +++ b/crates/codegen/src/symboltable.rs @@ -54,6 +54,9 @@ pub struct SymbolTable { /// Whether this type param scope can see the parent class scope pub can_see_class_scope: bool, + /// Whether this scope contains yield/yield from (is a generator function) + pub is_generator: bool, + /// Whether this comprehension scope should be inlined (PEP 709) /// True for list/set/dict comprehensions in non-generator expressions pub comp_inlined: bool, @@ -89,6 +92,7 @@ impl SymbolTable { needs_class_closure: false, needs_classdict: false, can_see_class_scope: false, + is_generator: false, comp_inlined: false, annotation_block: None, has_conditional_annotations: false, @@ -1823,6 +1827,7 @@ impl SymbolTableBuilder { node_index: _, range: _, }) => { + self.tables.last_mut().unwrap().is_generator = true; if let Some(expression) = value { self.scan_expression(expression, context)?; } @@ -1832,6 +1837,7 @@ impl SymbolTableBuilder { node_index: _, range: _, }) => { + self.tables.last_mut().unwrap().is_generator = true; self.scan_expression(value, context)?; } Expr::UnaryOp(ExprUnaryOp { diff --git a/crates/jit/src/instructions.rs b/crates/jit/src/instructions.rs index bc5c19c7d2b..956a385e29b 100644 --- a/crates/jit/src/instructions.rs +++ b/crates/jit/src/instructions.rs @@ -736,6 +736,14 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { let val = self.stack.pop().ok_or(JitCompileError::BadBytecode)?; self.store_variable(var_num.get(arg), val) } + Instruction::StoreFastStoreFast { var_nums } => { + let oparg = var_nums.get(arg); + let (idx1, idx2) = oparg.indexes(); + let val1 = self.stack.pop().ok_or(JitCompileError::BadBytecode)?; + self.store_variable(idx1, val1)?; + let val2 = self.stack.pop().ok_or(JitCompileError::BadBytecode)?; + self.store_variable(idx2, val2) + } Instruction::Swap { i: index } => { let len = self.stack.len(); let i = len - 1; diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index c38c6da11ad..9338c74d8f8 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -705,6 +705,18 @@ impl Frame { } } + // For generators/coroutines, initialize prev_line to the def line + // so that preamble instructions (RETURN_GENERATOR, POP_TOP) don't + // fire spurious LINE events. + let prev_line = if code + .flags + .intersects(bytecode::CodeFlags::GENERATOR | bytecode::CodeFlags::COROUTINE) + { + code.first_line_number.map_or(0, |line| line.get() as u32) + } else { + 0 + }; + let iframe = InterpreterFrame { localsplus, locals: match scope.locals { @@ -719,7 +731,7 @@ impl Frame { code, func_obj, lasti: Radium::new(0), - prev_line: 0, + prev_line, trace: PyMutex::new(vm.ctx.none()), trace_lines: PyMutex::new(true), trace_opcodes: PyMutex::new(false), @@ -2945,7 +2957,6 @@ impl ExecutingFrame<'_> { let bound = match vm.get_special_method(&obj, method_name)? { Some(PyMethod::Function { target, func }) => { - // Create bound method: PyBoundMethod(object=target, function=func) crate::builtins::PyBoundMethod::new(target, func) .into_ref(&vm.ctx) .into() @@ -3435,11 +3446,12 @@ impl ExecutingFrame<'_> { Instruction::StoreFastStoreFast { var_nums } => { let oparg = var_nums.get(arg); let (idx1, idx2) = oparg.indexes(); - let value1 = self.pop_value(); - let value2 = self.pop_value(); + // pop_value_opt: allows NULL from LoadFastAndClear restore path + let value1 = self.pop_value_opt(); + let value2 = self.pop_value_opt(); let fastlocals = self.localsplus.fastlocals_mut(); - fastlocals[idx1] = Some(value1); - fastlocals[idx2] = Some(value2); + fastlocals[idx1] = value1; + fastlocals[idx2] = value2; Ok(None) } Instruction::StoreGlobal { namei: idx } => {