Bytecode parity (#7481)

* Bytecode parity

 Compiler changes:
    - Remove PUSH_NULL from decorator cal
ls, use CALL 0
    - Collect __static_attributes__ from self.xxx = patterns
    - Sort __static_attributes__ alphabetically
    - Move __classdict__ init before __doc__ in class prologue
    - Fold unary negative constants
    - Fold constant list/set literals (3+ elements)
    - Use BUILD_MAP 0 + MAP_ADD for 16+ dict pairs
    - Always run peephole optimizer for s
uperinstructions
    - Emit RETURN_GENERATOR for generator
 functions
    - Add is_generator flag to SymbolTabl
e

* Fix formatting and collapsible_if clippy warnings in compile.rs

* Fix clippy, fold_unary_negative chaining, and generator line tracing

- Replace irrefutable if-let with let for ExceptHandler
- Remove folded UNARY_NEGATIVE instead of replacing with NOP,
  enabling chained negation folding
- Initialize prev_line to def line for generators/coroutines
  to suppress spurious LINE events from preamble instructions
- Remove expectedFailure markers for now-passing tests

* Fix JIT StoreFastStoreFast, format, and remove expectedFailure markers

- Add StoreFastStoreFast handling in JIT instructions
- Fix cargo fmt in frame.rs
- Remove 11 expectedFailure markers for async jump tests in
  test_sys_settrace that now pass

* Fix peephole optimizer: use NOP replacement instead of remove()

Using remove() shifts instruction indices and corrupts subsequent
references, causing "pop stackref but null found" panics at runtime.
Replace folded/combined instructions with NOP instead, which are
cleaned up by the existing remove_nops pass.

* Revert peephole_optimize to use remove() for chaining support

NOP replacement broke chaining of peephole optimizations (e.g.
LOAD_CONST+TO_BOOL then LOAD_CONST+UNARY_NOT for 'not True').
The remove() approach is used by upstream and works correctly here;
fold_unary_negative keeps NOP replacement since it doesn't need chaining.

* Fix StoreFastStoreFast to handle NULL from LoadFastAndClear

StoreFast uses pop_value_opt() to allow NULL values from
LoadFastAndClear in inlined comprehension cleanup paths.
StoreFastStoreFast must do the same, otherwise the peephole
optimizer's fusion of two StoreFast instructions panics when
restoring unbound locals after an inlined comprehension.
This commit is contained in:
Jeong, YunWon authored and GitHub committed 2026-03-25 16:10:19 +09:00
1 parent 0504ea2675
commit a293e11238
12 files changed
+519 -209

No files matched your search

-4
View File
@@ -2486,7 +2486,6 @@ class TestSourcePositions(unittest.TestCase):
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 @@ class TestStaticAttributes(unittest.TestCase):
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 @@ class TestStaticAttributes(unittest.TestCase):
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 @@ class TestExpressionStackSize(unittest.TestCase):
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}")
-1
View File
@@ -862,7 +862,6 @@ class TestMarkingVariablesAsUnKnown(BytecodeTestCase):
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
-22
View File
@@ -1420,8 +1420,6 @@ class JumpTestCase(unittest.TestCase):
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 @@ class JumpTestCase(unittest.TestCase):
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 @@ class JumpTestCase(unittest.TestCase):
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 @@ class JumpTestCase(unittest.TestCase):
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 @@ class JumpTestCase(unittest.TestCase):
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 @@ class JumpTestCase(unittest.TestCase):
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 @@ class JumpTestCase(unittest.TestCase):
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 @@ class JumpTestCase(unittest.TestCase):
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 @@ class JumpTestCase(unittest.TestCase):
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 @@ class JumpTestCase(unittest.TestCase):
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 @@ class JumpTestCase(unittest.TestCase):
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)
+138 -32
View File
@@ -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<String>>) {
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<String>) {
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<String> = self
let mut attrs: Vec<String> = 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(());
}
+250 -4
View File
@@ -189,17 +189,19 @@ impl CodeInfo {
mut self,
opts: &crate::compile::CompileOpts,
) -> crate::InternalResult<CodeObject> {
// 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 {
@@ -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
@@ -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
@@ -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
@@ -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)
+6
View File
@@ -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 {
+8
View File
@@ -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;
+18 -6
View File
@@ -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 } => {