Skip to content

Commit 40a43f3

Browse files
authored
instruction improvements (#6829)
New Features Direct small-integer loading (0–255) and locals-loading for faster execution Async-generator wrapping and improved generator resume behavior Performance Faster integer loads and streamlined jump/loop handling for better runtime performance Bug Fixes More robust StopIteration handling and stricter init return checks Corrected iterator cleanup for async and sync loops Improvements Aligns loop and jump semantics with CPython 3.14 patterns
1 parent aed3a60 commit 40a43f3

File tree

13 files changed

+433
-253
lines changed

13 files changed

+433
-253
lines changed

Lib/_opcode_metadata.py

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,12 @@
135135
'BINARY_OP_EXTEND': 132,
136136
'BINARY_OP_MULTIPLY_FLOAT': 133,
137137
'BINARY_OP_MULTIPLY_INT': 134,
138-
'BINARY_SUBSCR_DICT': 135,
139-
'BINARY_SUBSCR_GETITEM': 136,
140-
'BINARY_SUBSCR_LIST_INT': 137,
141-
'BINARY_SUBSCR_LIST_SLICE': 138,
142-
'BINARY_SUBSCR_STR_INT': 139,
143-
'BINARY_SUBSCR_TUPLE_INT': 140,
138+
'BINARY_OP_SUBSCR_DICT': 135,
139+
'BINARY_OP_SUBSCR_GETITEM': 136,
140+
'BINARY_OP_SUBSCR_LIST_INT': 137,
141+
'BINARY_OP_SUBSCR_LIST_SLICE': 138,
142+
'BINARY_OP_SUBSCR_STR_INT': 139,
143+
'BINARY_OP_SUBSCR_TUPLE_INT': 140,
144144
'BINARY_OP_SUBTRACT_FLOAT': 141,
145145
'BINARY_OP_SUBTRACT_INT': 142,
146146
'CALL_ALLOC_AND_ENTER_INIT': 143,
@@ -234,19 +234,21 @@
234234
'INSTRUMENTED_JUMP_BACKWARD': 253,
235235
'INSTRUMENTED_LINE': 254,
236236
'ENTER_EXECUTOR': 255,
237-
'JUMP': 256,
238-
'JUMP_NO_INTERRUPT': 257,
239-
'RESERVED_258': 258,
240-
'LOAD_ATTR_METHOD': 259,
241-
'LOAD_SUPER_METHOD': 260,
242-
'LOAD_ZERO_SUPER_ATTR': 261,
243-
'LOAD_ZERO_SUPER_METHOD': 262,
244-
'POP_BLOCK': 263,
245-
'SETUP_CLEANUP': 264,
246-
'SETUP_FINALLY': 265,
247-
'SETUP_WITH': 266,
248-
'STORE_FAST_MAYBE_NULL': 267,
249-
'LOAD_CLOSURE': 268,
237+
'ANNOTATIONS_PLACEHOLDER': 256,
238+
'JUMP': 257,
239+
'JUMP_IF_FALSE': 258,
240+
'JUMP_IF_TRUE': 259,
241+
'JUMP_NO_INTERRUPT': 260,
242+
'LOAD_CLOSURE': 261,
243+
'POP_BLOCK': 262,
244+
'SETUP_CLEANUP': 263,
245+
'SETUP_FINALLY': 264,
246+
'SETUP_WITH': 265,
247+
'STORE_FAST_MAYBE_NULL': 266,
248+
'LOAD_ATTR_METHOD': 267,
249+
'LOAD_SUPER_METHOD': 268,
250+
'LOAD_ZERO_SUPER_ATTR': 269,
251+
'LOAD_ZERO_SUPER_METHOD': 270,
250252
}
251253

252254
# CPython 3.13 compatible: opcodes < 44 have no argument

Lib/test/test__opcode.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ def test_stack_effect(self):
8383
self.assertRaises(ValueError, stack_effect, code)
8484
self.assertRaises(ValueError, stack_effect, code, 0)
8585

86-
@unittest.expectedFailure # TODO: RUSTPYTHON
8786
def test_stack_effect_jump(self):
8887
FOR_ITER = dis.opmap['FOR_ITER']
8988
self.assertEqual(stack_effect(FOR_ITER, 0), 1)

Lib/test/test_dis.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2185,24 +2185,28 @@ def test_bytecode_co_positions(self):
21852185
assert instr.positions == positions
21862186

21872187
class TestBytecodeTestCase(BytecodeTestCase):
2188+
@unittest.expectedFailure # TODO: RUSTPYTHON; RETURN_VALUE
21882189
def test_assert_not_in_with_op_not_in_bytecode(self):
21892190
code = compile("a = 1", "<string>", "exec")
21902191
self.assertInBytecode(code, "LOAD_CONST", 1)
21912192
self.assertNotInBytecode(code, "LOAD_NAME")
21922193
self.assertNotInBytecode(code, "LOAD_NAME", "a")
21932194

2195+
@unittest.expectedFailure # TODO: RUSTPYTHON; RETURN_VALUE
21942196
def test_assert_not_in_with_arg_not_in_bytecode(self):
21952197
code = compile("a = 1", "<string>", "exec")
21962198
self.assertInBytecode(code, "LOAD_CONST")
21972199
self.assertInBytecode(code, "LOAD_CONST", 1)
21982200
self.assertNotInBytecode(code, "LOAD_CONST", 2)
21992201

2202+
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: AssertionError not raised
22002203
def test_assert_not_in_with_arg_in_bytecode(self):
22012204
code = compile("a = 1", "<string>", "exec")
22022205
with self.assertRaises(AssertionError):
22032206
self.assertNotInBytecode(code, "LOAD_CONST", 1)
22042207

22052208
class TestFinderMethods(unittest.TestCase):
2209+
@unittest.expectedFailure # TODO: RUSTPYTHON
22062210
def test__find_imports(self):
22072211
cases = [
22082212
("import a.b.c", ('a.b.c', 0, None)),

Lib/test/test_import/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1385,7 +1385,7 @@ class PycRewritingTests(unittest.TestCase):
13851385
import sys
13861386
code_filename = sys._getframe().f_code.co_filename
13871387
module_filename = __file__
1388-
constant = 1
1388+
constant = 1000
13891389
def func():
13901390
pass
13911391
func_filename = func.__code__.co_filename
@@ -1455,7 +1455,7 @@ def test_foreign_code(self):
14551455
code = marshal.load(f)
14561456
constants = list(code.co_consts)
14571457
foreign_code = importlib.import_module.__code__
1458-
pos = constants.index(1)
1458+
pos = constants.index(1000)
14591459
constants[pos] = foreign_code
14601460
code = code.replace(co_consts=tuple(constants))
14611461
with open(self.compiled_name, "wb") as f:

crates/codegen/src/compile.rs

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,6 +1136,13 @@ impl Compiler {
11361136
// Set the source range for the RESUME instruction
11371137
// For now, just use an empty range at the beginning
11381138
self.current_source_range = TextRange::default();
1139+
1140+
// For async functions/coroutines, emit RETURN_GENERATOR + POP_TOP before RESUME
1141+
if scope_type == CompilerScope::AsyncFunction {
1142+
emit!(self, Instruction::ReturnGenerator);
1143+
emit!(self, Instruction::PopTop);
1144+
}
1145+
11391146
emit!(
11401147
self,
11411148
Instruction::Resume {
@@ -1371,7 +1378,7 @@ impl Compiler {
13711378
if preserve_tos {
13721379
emit!(self, Instruction::Swap { index: 2 });
13731380
}
1374-
emit!(self, Instruction::PopTop);
1381+
emit!(self, Instruction::PopIter);
13751382
}
13761383

13771384
FBlockType::TryExcept => {
@@ -3623,6 +3630,7 @@ impl Compiler {
36233630
self.current_code_info().flags |= bytecode::CodeFlags::HAS_DOCSTRING;
36243631
}
36253632
// If no docstring, don't add None to co_consts
3633+
// Note: RETURN_GENERATOR + POP_TOP for async functions is emitted in enter_scope()
36263634

36273635
// Compile body statements
36283636
self.compile_statements(body)?;
@@ -4348,10 +4356,7 @@ impl Compiler {
43484356

43494357
// PEP 649: Initialize __classdict__ cell for class annotation scope
43504358
if self.current_symbol_table().needs_classdict {
4351-
let locals_name = self.name("locals");
4352-
emit!(self, Instruction::LoadName(locals_name));
4353-
emit!(self, Instruction::PushNull);
4354-
emit!(self, Instruction::Call { nargs: 0 });
4359+
emit!(self, Instruction::LoadLocals);
43554360
let classdict_idx = self.get_cell_var_index("__classdict__")?;
43564361
emit!(self, Instruction::StoreDeref(classdict_idx));
43574362
}
@@ -4978,8 +4983,10 @@ impl Compiler {
49784983
if is_async {
49794984
emit!(self, Instruction::EndAsyncFor);
49804985
} else {
4981-
// Pop the iterator after loop ends
4982-
emit!(self, Instruction::PopTop);
4986+
// END_FOR + POP_ITER pattern (CPython 3.14)
4987+
// FOR_ITER jumps to END_FOR, but VM skips it (+1) to reach POP_ITER
4988+
emit!(self, Instruction::EndFor);
4989+
emit!(self, Instruction::PopIter);
49834990
}
49844991
self.compile_statements(orelse)?;
49854992

@@ -6527,8 +6534,11 @@ impl Compiler {
65276534
}
65286535
);
65296536

6530-
// JUMP_NO_INTERRUPT send (regular JUMP in RustPython)
6531-
emit!(self, PseudoInstruction::Jump { target: send_block });
6537+
// JUMP_BACKWARD_NO_INTERRUPT send
6538+
emit!(
6539+
self,
6540+
PseudoInstruction::JumpNoInterrupt { target: send_block }
6541+
);
65326542

65336543
// fail: CLEANUP_THROW
65346544
// Stack when exception: [receiver, yielded_value, exc]
@@ -7424,7 +7434,9 @@ impl Compiler {
74247434
emit!(self, Instruction::EndAsyncFor);
74257435
emit!(self, Instruction::PopTop);
74267436
} else {
7427-
emit!(self, Instruction::PopTop);
7437+
// END_FOR + POP_ITER pattern (CPython 3.14)
7438+
emit!(self, Instruction::EndFor);
7439+
emit!(self, Instruction::PopIter);
74287440
}
74297441
}
74307442

@@ -7621,9 +7633,13 @@ impl Compiler {
76217633
self.switch_to_block(after_block);
76227634
if is_async {
76237635
emit!(self, Instruction::EndAsyncFor);
7636+
// Pop the iterator
7637+
emit!(self, Instruction::PopTop);
7638+
} else {
7639+
// END_FOR + POP_ITER pattern (CPython 3.14)
7640+
emit!(self, Instruction::EndFor);
7641+
emit!(self, Instruction::PopIter);
76247642
}
7625-
// Pop the iterator
7626-
emit!(self, Instruction::PopTop);
76277643
}
76287644

76297645
// Step 8: Clean up - restore saved locals
@@ -7741,6 +7757,18 @@ impl Compiler {
77417757
}
77427758

77437759
fn emit_load_const(&mut self, constant: ConstantData) {
7760+
// Use LOAD_SMALL_INT for integers in small int cache range (-5..=256)
7761+
// Still add to co_consts for compatibility (CPython does this too)
7762+
if let ConstantData::Integer { ref value } = constant
7763+
&& let Some(small_int) = value.to_i32()
7764+
&& (-5..=256).contains(&small_int)
7765+
{
7766+
// Add to co_consts even though we use LOAD_SMALL_INT
7767+
let _idx = self.arg_constant(constant);
7768+
// Store as u32 (two's complement for negative values)
7769+
self.emit_arg(small_int as u32, |idx| Instruction::LoadSmallInt { idx });
7770+
return;
7771+
}
77447772
let idx = self.arg_constant(constant);
77457773
self.emit_arg(idx, |idx| Instruction::LoadConst { idx })
77467774
}
@@ -7924,7 +7952,7 @@ impl Compiler {
79247952

79257953
// For break in a for loop, pop the iterator
79267954
if is_break && is_for_loop {
7927-
emit!(self, Instruction::PopTop);
7955+
emit!(self, Instruction::PopIter);
79287956
}
79297957

79307958
// Jump to target

crates/codegen/src/ir.rs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -270,15 +270,16 @@ impl CodeInfo {
270270
info.arg = OpArg(new_idx);
271271
info.instr = Instruction::LoadFast(Arg::marker()).into();
272272
}
273-
PseudoInstruction::Jump { .. } => {
274-
// PseudoInstruction::Jump instructions are handled later
273+
PseudoInstruction::Jump { .. } | PseudoInstruction::JumpNoInterrupt { .. } => {
274+
// Jump pseudo instructions are handled later
275275
}
276-
PseudoInstruction::JumpNoInterrupt { .. }
277-
| PseudoInstruction::Reserved258
276+
PseudoInstruction::AnnotationsPlaceholder
277+
| PseudoInstruction::JumpIfFalse { .. }
278+
| PseudoInstruction::JumpIfTrue { .. }
278279
| PseudoInstruction::SetupCleanup
279280
| PseudoInstruction::SetupFinally
280281
| PseudoInstruction::SetupWith
281-
| PseudoInstruction::StoreFastMaybeNull => {
282+
| PseudoInstruction::StoreFastMaybeNull(_) => {
282283
unimplemented!("Got a placeholder pseudo instruction ({instr:?})")
283284
}
284285
}
@@ -335,6 +336,14 @@ impl CodeInfo {
335336
}
336337
}
337338
}
339+
AnyInstruction::Pseudo(PseudoInstruction::JumpNoInterrupt { .. })
340+
if target != BlockIdx::NULL =>
341+
{
342+
// JumpNoInterrupt is always backward (used in yield-from/await loops)
343+
Instruction::JumpBackwardNoInterrupt {
344+
target: Arg::marker(),
345+
}
346+
}
338347
other => other.expect_real(),
339348
};
340349

@@ -468,7 +477,7 @@ impl CodeInfo {
468477
let block = &self.blocks[block_idx];
469478
for ins in &block.instructions {
470479
let instr = &ins.instr;
471-
let effect = instr.stack_effect(ins.arg, false);
480+
let effect = instr.stack_effect(ins.arg);
472481
if DEBUG {
473482
let display_arg = if ins.target == BlockIdx::NULL {
474483
ins.arg
@@ -493,7 +502,7 @@ impl CodeInfo {
493502
}
494503
// Process target blocks for branching instructions
495504
if ins.target != BlockIdx::NULL {
496-
let effect = instr.stack_effect(ins.arg, true);
505+
// Both jump and non-jump paths have the same stack effect
497506
let target_depth = depth.checked_add_signed(effect).ok_or({
498507
if effect < 0 {
499508
InternalError::StackUnderflow

0 commit comments

Comments
 (0)