Skip to content

Commit eac4572

Browse files
authored
Bytecode parity - direct loop backedges (RustPython#7578)
1 parent 28acbc6 commit eac4572

3 files changed

Lines changed: 427 additions & 98 deletions

File tree

crates/codegen/src/compile.rs

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1512,11 +1512,14 @@ impl Compiler {
15121512
}
15131513

15141514
FBlockType::ForLoop => {
1515-
// Pop the iterator
1515+
// When returning from a for-loop, CPython swaps the preserved
1516+
// value with the iterator and uses POP_TOP for the iterator slot.
15161517
if preserve_tos {
15171518
emit!(self, Instruction::Swap { i: 2 });
1519+
emit!(self, Instruction::PopTop);
1520+
} else {
1521+
emit!(self, Instruction::PopIter);
15181522
}
1519-
emit!(self, Instruction::PopIter);
15201523
}
15211524

15221525
FBlockType::TryExcept => {
@@ -11148,6 +11151,99 @@ def f(base, cls, state):
1114811151
assert_eq!(return_count, 2);
1114911152
}
1115011153

11154+
#[test]
11155+
fn test_loop_store_subscr_threads_direct_backedge() {
11156+
let code = compile_exec(
11157+
"\
11158+
def f(kwonlyargs, kwonlydefaults, arg2value):
11159+
missing = 0
11160+
for kwarg in kwonlyargs:
11161+
if kwarg not in arg2value:
11162+
if kwonlydefaults and kwarg in kwonlydefaults:
11163+
arg2value[kwarg] = kwonlydefaults[kwarg]
11164+
else:
11165+
missing += 1
11166+
return missing
11167+
",
11168+
);
11169+
let f = find_code(&code, "f").expect("missing function code");
11170+
let ops: Vec<_> = f
11171+
.instructions
11172+
.iter()
11173+
.map(|unit| unit.op)
11174+
.filter(|op| !matches!(op, Instruction::Cache))
11175+
.collect();
11176+
11177+
let store_subscr = ops
11178+
.iter()
11179+
.position(|op| matches!(op, Instruction::StoreSubscr))
11180+
.expect("missing STORE_SUBSCR");
11181+
let next_op = ops
11182+
.get(store_subscr + 1)
11183+
.expect("missing jump after STORE_SUBSCR");
11184+
let window_start = store_subscr.saturating_sub(3);
11185+
let window_end = (store_subscr + 5).min(ops.len());
11186+
let window = &ops[window_start..window_end];
11187+
11188+
assert!(
11189+
matches!(next_op, Instruction::JumpBackward { .. }),
11190+
"expected direct loop backedge after STORE_SUBSCR, got {next_op:?}; ops={window:?}"
11191+
);
11192+
}
11193+
11194+
#[test]
11195+
fn test_loop_return_reorders_backedge_before_exit_cleanup() {
11196+
let code = compile_exec(
11197+
"\
11198+
def f(obj):
11199+
for base in obj.__mro__:
11200+
if base is not object:
11201+
doc = base.__doc__
11202+
if doc is not None:
11203+
return doc
11204+
",
11205+
);
11206+
let f = find_code(&code, "f").expect("missing function code");
11207+
let ops: Vec<_> = f
11208+
.instructions
11209+
.iter()
11210+
.map(|unit| unit.op)
11211+
.filter(|op| !matches!(op, Instruction::Cache))
11212+
.collect();
11213+
11214+
let cond_idx = ops
11215+
.iter()
11216+
.position(|op| matches!(op, Instruction::PopJumpIfNotNone { .. }))
11217+
.expect("missing POP_JUMP_IF_NOT_NONE");
11218+
assert!(
11219+
matches!(ops.get(cond_idx + 1), Some(Instruction::NotTaken)),
11220+
"expected NOT_TAKEN after conditional jump, got {:?}; ops={ops:?}",
11221+
ops.get(cond_idx + 1)
11222+
);
11223+
assert!(
11224+
matches!(
11225+
ops.get(cond_idx + 2),
11226+
Some(Instruction::JumpBackward { .. })
11227+
),
11228+
"expected loop backedge immediately after NOT_TAKEN, got {:?}; ops={ops:?}",
11229+
ops.get(cond_idx + 2)
11230+
);
11231+
11232+
let end_for_idx = ops
11233+
.iter()
11234+
.position(|op| matches!(op, Instruction::EndFor))
11235+
.expect("missing END_FOR");
11236+
let return_before_end = ops[..end_for_idx]
11237+
.iter()
11238+
.rposition(|op| matches!(op, Instruction::ReturnValue))
11239+
.expect("missing loop-body RETURN_VALUE");
11240+
assert!(
11241+
matches!(ops.get(return_before_end - 1), Some(Instruction::PopTop)),
11242+
"expected POP_TOP before loop-body RETURN_VALUE, got {:?}; ops={ops:?}",
11243+
ops.get(return_before_end.saturating_sub(1))
11244+
);
11245+
}
11246+
1115111247
#[test]
1115211248
fn test_assert_without_message_raises_class_directly() {
1115311249
let code = compile_exec(

0 commit comments

Comments
 (0)