mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-29 03:04:38 +08:00
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
This commit is contained in:
1 parent
aed3a60e17
commit
40a43f3210
13 files changed
+433
-253
No files matched your search
Vendored
+21
-19
@@ -135,12 +135,12 @@ opmap = {
|
||||
'BINARY_OP_EXTEND': 132,
|
||||
'BINARY_OP_MULTIPLY_FLOAT': 133,
|
||||
'BINARY_OP_MULTIPLY_INT': 134,
|
||||
'BINARY_SUBSCR_DICT': 135,
|
||||
'BINARY_SUBSCR_GETITEM': 136,
|
||||
'BINARY_SUBSCR_LIST_INT': 137,
|
||||
'BINARY_SUBSCR_LIST_SLICE': 138,
|
||||
'BINARY_SUBSCR_STR_INT': 139,
|
||||
'BINARY_SUBSCR_TUPLE_INT': 140,
|
||||
'BINARY_OP_SUBSCR_DICT': 135,
|
||||
'BINARY_OP_SUBSCR_GETITEM': 136,
|
||||
'BINARY_OP_SUBSCR_LIST_INT': 137,
|
||||
'BINARY_OP_SUBSCR_LIST_SLICE': 138,
|
||||
'BINARY_OP_SUBSCR_STR_INT': 139,
|
||||
'BINARY_OP_SUBSCR_TUPLE_INT': 140,
|
||||
'BINARY_OP_SUBTRACT_FLOAT': 141,
|
||||
'BINARY_OP_SUBTRACT_INT': 142,
|
||||
'CALL_ALLOC_AND_ENTER_INIT': 143,
|
||||
@@ -234,19 +234,21 @@ opmap = {
|
||||
'INSTRUMENTED_JUMP_BACKWARD': 253,
|
||||
'INSTRUMENTED_LINE': 254,
|
||||
'ENTER_EXECUTOR': 255,
|
||||
'JUMP': 256,
|
||||
'JUMP_NO_INTERRUPT': 257,
|
||||
'RESERVED_258': 258,
|
||||
'LOAD_ATTR_METHOD': 259,
|
||||
'LOAD_SUPER_METHOD': 260,
|
||||
'LOAD_ZERO_SUPER_ATTR': 261,
|
||||
'LOAD_ZERO_SUPER_METHOD': 262,
|
||||
'POP_BLOCK': 263,
|
||||
'SETUP_CLEANUP': 264,
|
||||
'SETUP_FINALLY': 265,
|
||||
'SETUP_WITH': 266,
|
||||
'STORE_FAST_MAYBE_NULL': 267,
|
||||
'LOAD_CLOSURE': 268,
|
||||
'ANNOTATIONS_PLACEHOLDER': 256,
|
||||
'JUMP': 257,
|
||||
'JUMP_IF_FALSE': 258,
|
||||
'JUMP_IF_TRUE': 259,
|
||||
'JUMP_NO_INTERRUPT': 260,
|
||||
'LOAD_CLOSURE': 261,
|
||||
'POP_BLOCK': 262,
|
||||
'SETUP_CLEANUP': 263,
|
||||
'SETUP_FINALLY': 264,
|
||||
'SETUP_WITH': 265,
|
||||
'STORE_FAST_MAYBE_NULL': 266,
|
||||
'LOAD_ATTR_METHOD': 267,
|
||||
'LOAD_SUPER_METHOD': 268,
|
||||
'LOAD_ZERO_SUPER_ATTR': 269,
|
||||
'LOAD_ZERO_SUPER_METHOD': 270,
|
||||
}
|
||||
|
||||
# CPython 3.13 compatible: opcodes < 44 have no argument
|
||||
|
||||
Vendored
-1
@@ -83,7 +83,6 @@ class StackEffectTests(unittest.TestCase):
|
||||
self.assertRaises(ValueError, stack_effect, code)
|
||||
self.assertRaises(ValueError, stack_effect, code, 0)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_stack_effect_jump(self):
|
||||
FOR_ITER = dis.opmap['FOR_ITER']
|
||||
self.assertEqual(stack_effect(FOR_ITER, 0), 1)
|
||||
|
||||
Vendored
+4
@@ -2185,24 +2185,28 @@ class BytecodeTests(InstructionTestCase, DisTestBase):
|
||||
assert instr.positions == positions
|
||||
|
||||
class TestBytecodeTestCase(BytecodeTestCase):
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; RETURN_VALUE
|
||||
def test_assert_not_in_with_op_not_in_bytecode(self):
|
||||
code = compile("a = 1", "<string>", "exec")
|
||||
self.assertInBytecode(code, "LOAD_CONST", 1)
|
||||
self.assertNotInBytecode(code, "LOAD_NAME")
|
||||
self.assertNotInBytecode(code, "LOAD_NAME", "a")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; RETURN_VALUE
|
||||
def test_assert_not_in_with_arg_not_in_bytecode(self):
|
||||
code = compile("a = 1", "<string>", "exec")
|
||||
self.assertInBytecode(code, "LOAD_CONST")
|
||||
self.assertInBytecode(code, "LOAD_CONST", 1)
|
||||
self.assertNotInBytecode(code, "LOAD_CONST", 2)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: AssertionError not raised
|
||||
def test_assert_not_in_with_arg_in_bytecode(self):
|
||||
code = compile("a = 1", "<string>", "exec")
|
||||
with self.assertRaises(AssertionError):
|
||||
self.assertNotInBytecode(code, "LOAD_CONST", 1)
|
||||
|
||||
class TestFinderMethods(unittest.TestCase):
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test__find_imports(self):
|
||||
cases = [
|
||||
("import a.b.c", ('a.b.c', 0, None)),
|
||||
|
||||
Vendored
+2
-2
@@ -1385,7 +1385,7 @@ class PycRewritingTests(unittest.TestCase):
|
||||
import sys
|
||||
code_filename = sys._getframe().f_code.co_filename
|
||||
module_filename = __file__
|
||||
constant = 1
|
||||
constant = 1000
|
||||
def func():
|
||||
pass
|
||||
func_filename = func.__code__.co_filename
|
||||
@@ -1455,7 +1455,7 @@ func_filename = func.__code__.co_filename
|
||||
code = marshal.load(f)
|
||||
constants = list(code.co_consts)
|
||||
foreign_code = importlib.import_module.__code__
|
||||
pos = constants.index(1)
|
||||
pos = constants.index(1000)
|
||||
constants[pos] = foreign_code
|
||||
code = code.replace(co_consts=tuple(constants))
|
||||
with open(self.compiled_name, "wb") as f:
|
||||
|
||||
@@ -1136,6 +1136,13 @@ impl Compiler {
|
||||
// Set the source range for the RESUME instruction
|
||||
// For now, just use an empty range at the beginning
|
||||
self.current_source_range = TextRange::default();
|
||||
|
||||
// For async functions/coroutines, emit RETURN_GENERATOR + POP_TOP before RESUME
|
||||
if scope_type == CompilerScope::AsyncFunction {
|
||||
emit!(self, Instruction::ReturnGenerator);
|
||||
emit!(self, Instruction::PopTop);
|
||||
}
|
||||
|
||||
emit!(
|
||||
self,
|
||||
Instruction::Resume {
|
||||
@@ -1371,7 +1378,7 @@ impl Compiler {
|
||||
if preserve_tos {
|
||||
emit!(self, Instruction::Swap { index: 2 });
|
||||
}
|
||||
emit!(self, Instruction::PopTop);
|
||||
emit!(self, Instruction::PopIter);
|
||||
}
|
||||
|
||||
FBlockType::TryExcept => {
|
||||
@@ -3623,6 +3630,7 @@ impl Compiler {
|
||||
self.current_code_info().flags |= bytecode::CodeFlags::HAS_DOCSTRING;
|
||||
}
|
||||
// If no docstring, don't add None to co_consts
|
||||
// Note: RETURN_GENERATOR + POP_TOP for async functions is emitted in enter_scope()
|
||||
|
||||
// Compile body statements
|
||||
self.compile_statements(body)?;
|
||||
@@ -4348,10 +4356,7 @@ impl Compiler {
|
||||
|
||||
// PEP 649: Initialize __classdict__ cell for class annotation scope
|
||||
if self.current_symbol_table().needs_classdict {
|
||||
let locals_name = self.name("locals");
|
||||
emit!(self, Instruction::LoadName(locals_name));
|
||||
emit!(self, Instruction::PushNull);
|
||||
emit!(self, Instruction::Call { nargs: 0 });
|
||||
emit!(self, Instruction::LoadLocals);
|
||||
let classdict_idx = self.get_cell_var_index("__classdict__")?;
|
||||
emit!(self, Instruction::StoreDeref(classdict_idx));
|
||||
}
|
||||
@@ -4978,8 +4983,10 @@ impl Compiler {
|
||||
if is_async {
|
||||
emit!(self, Instruction::EndAsyncFor);
|
||||
} else {
|
||||
// Pop the iterator after loop ends
|
||||
emit!(self, Instruction::PopTop);
|
||||
// END_FOR + POP_ITER pattern (CPython 3.14)
|
||||
// FOR_ITER jumps to END_FOR, but VM skips it (+1) to reach POP_ITER
|
||||
emit!(self, Instruction::EndFor);
|
||||
emit!(self, Instruction::PopIter);
|
||||
}
|
||||
self.compile_statements(orelse)?;
|
||||
|
||||
@@ -6527,8 +6534,11 @@ impl Compiler {
|
||||
}
|
||||
);
|
||||
|
||||
// JUMP_NO_INTERRUPT send (regular JUMP in RustPython)
|
||||
emit!(self, PseudoInstruction::Jump { target: send_block });
|
||||
// JUMP_BACKWARD_NO_INTERRUPT send
|
||||
emit!(
|
||||
self,
|
||||
PseudoInstruction::JumpNoInterrupt { target: send_block }
|
||||
);
|
||||
|
||||
// fail: CLEANUP_THROW
|
||||
// Stack when exception: [receiver, yielded_value, exc]
|
||||
@@ -7424,7 +7434,9 @@ impl Compiler {
|
||||
emit!(self, Instruction::EndAsyncFor);
|
||||
emit!(self, Instruction::PopTop);
|
||||
} else {
|
||||
emit!(self, Instruction::PopTop);
|
||||
// END_FOR + POP_ITER pattern (CPython 3.14)
|
||||
emit!(self, Instruction::EndFor);
|
||||
emit!(self, Instruction::PopIter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7621,9 +7633,13 @@ impl Compiler {
|
||||
self.switch_to_block(after_block);
|
||||
if is_async {
|
||||
emit!(self, Instruction::EndAsyncFor);
|
||||
// Pop the iterator
|
||||
emit!(self, Instruction::PopTop);
|
||||
} else {
|
||||
// END_FOR + POP_ITER pattern (CPython 3.14)
|
||||
emit!(self, Instruction::EndFor);
|
||||
emit!(self, Instruction::PopIter);
|
||||
}
|
||||
// Pop the iterator
|
||||
emit!(self, Instruction::PopTop);
|
||||
}
|
||||
|
||||
// Step 8: Clean up - restore saved locals
|
||||
@@ -7741,6 +7757,18 @@ impl Compiler {
|
||||
}
|
||||
|
||||
fn emit_load_const(&mut self, constant: ConstantData) {
|
||||
// Use LOAD_SMALL_INT for integers in small int cache range (-5..=256)
|
||||
// Still add to co_consts for compatibility (CPython does this too)
|
||||
if let ConstantData::Integer { ref value } = constant
|
||||
&& let Some(small_int) = value.to_i32()
|
||||
&& (-5..=256).contains(&small_int)
|
||||
{
|
||||
// Add to co_consts even though we use LOAD_SMALL_INT
|
||||
let _idx = self.arg_constant(constant);
|
||||
// Store as u32 (two's complement for negative values)
|
||||
self.emit_arg(small_int as u32, |idx| Instruction::LoadSmallInt { idx });
|
||||
return;
|
||||
}
|
||||
let idx = self.arg_constant(constant);
|
||||
self.emit_arg(idx, |idx| Instruction::LoadConst { idx })
|
||||
}
|
||||
@@ -7924,7 +7952,7 @@ impl Compiler {
|
||||
|
||||
// For break in a for loop, pop the iterator
|
||||
if is_break && is_for_loop {
|
||||
emit!(self, Instruction::PopTop);
|
||||
emit!(self, Instruction::PopIter);
|
||||
}
|
||||
|
||||
// Jump to target
|
||||
|
||||
@@ -270,15 +270,16 @@ impl CodeInfo {
|
||||
info.arg = OpArg(new_idx);
|
||||
info.instr = Instruction::LoadFast(Arg::marker()).into();
|
||||
}
|
||||
PseudoInstruction::Jump { .. } => {
|
||||
// PseudoInstruction::Jump instructions are handled later
|
||||
PseudoInstruction::Jump { .. } | PseudoInstruction::JumpNoInterrupt { .. } => {
|
||||
// Jump pseudo instructions are handled later
|
||||
}
|
||||
PseudoInstruction::JumpNoInterrupt { .. }
|
||||
| PseudoInstruction::Reserved258
|
||||
PseudoInstruction::AnnotationsPlaceholder
|
||||
| PseudoInstruction::JumpIfFalse { .. }
|
||||
| PseudoInstruction::JumpIfTrue { .. }
|
||||
| PseudoInstruction::SetupCleanup
|
||||
| PseudoInstruction::SetupFinally
|
||||
| PseudoInstruction::SetupWith
|
||||
| PseudoInstruction::StoreFastMaybeNull => {
|
||||
| PseudoInstruction::StoreFastMaybeNull(_) => {
|
||||
unimplemented!("Got a placeholder pseudo instruction ({instr:?})")
|
||||
}
|
||||
}
|
||||
@@ -335,6 +336,14 @@ impl CodeInfo {
|
||||
}
|
||||
}
|
||||
}
|
||||
AnyInstruction::Pseudo(PseudoInstruction::JumpNoInterrupt { .. })
|
||||
if target != BlockIdx::NULL =>
|
||||
{
|
||||
// JumpNoInterrupt is always backward (used in yield-from/await loops)
|
||||
Instruction::JumpBackwardNoInterrupt {
|
||||
target: Arg::marker(),
|
||||
}
|
||||
}
|
||||
other => other.expect_real(),
|
||||
};
|
||||
|
||||
@@ -468,7 +477,7 @@ impl CodeInfo {
|
||||
let block = &self.blocks[block_idx];
|
||||
for ins in &block.instructions {
|
||||
let instr = &ins.instr;
|
||||
let effect = instr.stack_effect(ins.arg, false);
|
||||
let effect = instr.stack_effect(ins.arg);
|
||||
if DEBUG {
|
||||
let display_arg = if ins.target == BlockIdx::NULL {
|
||||
ins.arg
|
||||
@@ -493,7 +502,7 @@ impl CodeInfo {
|
||||
}
|
||||
// Process target blocks for branching instructions
|
||||
if ins.target != BlockIdx::NULL {
|
||||
let effect = instr.stack_effect(ins.arg, true);
|
||||
// Both jump and non-jump paths have the same stack effect
|
||||
let target_depth = depth.checked_add_signed(effect).ok_or({
|
||||
if effect < 0 {
|
||||
InternalError::StackUnderflow
|
||||
|
||||
Generated
+143
-141
@@ -1,159 +1,161 @@
|
||||
---
|
||||
source: crates/codegen/src/compile.rs
|
||||
assertion_line: 8655
|
||||
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\")"
|
||||
---
|
||||
3 0 LOAD_CONST (<code object test at ??? file "source_path", line 1>): 1 0 RESUME (0)
|
||||
3 0 LOAD_CONST (<code object test at ??? file "source_path", line 1>): 1 0 RUSTPYTHON_PLACEHOLDER
|
||||
1 POP_TOP
|
||||
2 RESUME (0)
|
||||
|
||||
2 1 LOAD_GLOBAL (0, StopIteration)
|
||||
2 PUSH_NULL
|
||||
3 LOAD_CONST ("spam")
|
||||
4 CALL (1)
|
||||
5 LOAD_GLOBAL (1, StopAsyncIteration)
|
||||
6 PUSH_NULL
|
||||
7 LOAD_CONST ("ham")
|
||||
8 CALL (1)
|
||||
9 BUILD_TUPLE (2)
|
||||
10 GET_ITER
|
||||
>> 11 FOR_ITER (139)
|
||||
12 STORE_FAST (0, stop_exc)
|
||||
2 3 LOAD_GLOBAL (0, StopIteration)
|
||||
4 PUSH_NULL
|
||||
5 LOAD_CONST ("spam")
|
||||
6 CALL (1)
|
||||
7 LOAD_GLOBAL (1, StopAsyncIteration)
|
||||
8 PUSH_NULL
|
||||
9 LOAD_CONST ("ham")
|
||||
10 CALL (1)
|
||||
11 BUILD_TUPLE (2)
|
||||
12 GET_ITER
|
||||
>> 13 FOR_ITER (141)
|
||||
14 STORE_FAST (0, stop_exc)
|
||||
|
||||
3 13 LOAD_GLOBAL (2, self)
|
||||
14 LOAD_ATTR (7, subTest, method=true)
|
||||
15 LOAD_GLOBAL (4, type)
|
||||
16 PUSH_NULL
|
||||
17 LOAD_FAST (0, stop_exc)
|
||||
18 CALL (1)
|
||||
19 LOAD_CONST (("type"))
|
||||
20 CALL_KW (1)
|
||||
21 COPY (1)
|
||||
22 LOAD_SPECIAL (__exit__)
|
||||
23 SWAP (2)
|
||||
24 LOAD_SPECIAL (__enter__)
|
||||
25 PUSH_NULL
|
||||
26 CALL (0)
|
||||
27 POP_TOP
|
||||
3 15 LOAD_GLOBAL (2, self)
|
||||
16 LOAD_ATTR (7, subTest, method=true)
|
||||
17 LOAD_GLOBAL (4, type)
|
||||
18 PUSH_NULL
|
||||
19 LOAD_FAST (0, stop_exc)
|
||||
20 CALL (1)
|
||||
21 LOAD_CONST (("type"))
|
||||
22 CALL_KW (1)
|
||||
23 COPY (1)
|
||||
24 LOAD_SPECIAL (__exit__)
|
||||
25 SWAP (2)
|
||||
26 LOAD_SPECIAL (__enter__)
|
||||
27 PUSH_NULL
|
||||
28 CALL (0)
|
||||
29 POP_TOP
|
||||
|
||||
5 28 LOAD_GLOBAL (5, egg)
|
||||
29 PUSH_NULL
|
||||
30 CALL (0)
|
||||
31 COPY (1)
|
||||
32 LOAD_SPECIAL (__aexit__)
|
||||
33 SWAP (2)
|
||||
34 LOAD_SPECIAL (__aenter__)
|
||||
35 PUSH_NULL
|
||||
36 CALL (0)
|
||||
37 GET_AWAITABLE
|
||||
38 LOAD_CONST (None)
|
||||
>> 39 SEND (44)
|
||||
40 YIELD_VALUE (1)
|
||||
41 RESUME (3)
|
||||
42 JUMP_BACKWARD (39)
|
||||
43 CLEANUP_THROW
|
||||
>> 44 END_SEND
|
||||
45 POP_TOP
|
||||
5 30 LOAD_GLOBAL (5, egg)
|
||||
31 PUSH_NULL
|
||||
32 CALL (0)
|
||||
33 COPY (1)
|
||||
34 LOAD_SPECIAL (__aexit__)
|
||||
35 SWAP (2)
|
||||
36 LOAD_SPECIAL (__aenter__)
|
||||
37 PUSH_NULL
|
||||
38 CALL (0)
|
||||
39 GET_AWAITABLE
|
||||
40 LOAD_CONST (None)
|
||||
>> 41 SEND (46)
|
||||
42 YIELD_VALUE (1)
|
||||
43 RESUME (3)
|
||||
44 JUMP_BACKWARD_NO_INTERRUPT(41)
|
||||
45 CLEANUP_THROW
|
||||
>> 46 END_SEND
|
||||
47 POP_TOP
|
||||
|
||||
6 46 LOAD_FAST (0, stop_exc)
|
||||
47 RAISE_VARARGS (Raise)
|
||||
6 48 LOAD_FAST (0, stop_exc)
|
||||
49 RAISE_VARARGS (Raise)
|
||||
|
||||
5 48 PUSH_NULL
|
||||
49 LOAD_CONST (None)
|
||||
50 LOAD_CONST (None)
|
||||
5 50 PUSH_NULL
|
||||
51 LOAD_CONST (None)
|
||||
52 CALL (3)
|
||||
53 GET_AWAITABLE
|
||||
54 LOAD_CONST (None)
|
||||
>> 55 SEND (60)
|
||||
56 YIELD_VALUE (1)
|
||||
57 RESUME (3)
|
||||
58 JUMP_BACKWARD (55)
|
||||
59 CLEANUP_THROW
|
||||
>> 60 END_SEND
|
||||
61 POP_TOP
|
||||
62 JUMP_FORWARD (84)
|
||||
63 PUSH_EXC_INFO
|
||||
64 WITH_EXCEPT_START
|
||||
65 GET_AWAITABLE
|
||||
66 LOAD_CONST (None)
|
||||
>> 67 SEND (72)
|
||||
68 YIELD_VALUE (1)
|
||||
69 RESUME (3)
|
||||
70 JUMP_BACKWARD (67)
|
||||
71 CLEANUP_THROW
|
||||
>> 72 END_SEND
|
||||
73 TO_BOOL
|
||||
74 POP_JUMP_IF_TRUE (76)
|
||||
75 RERAISE (2)
|
||||
>> 76 POP_TOP
|
||||
77 POP_EXCEPT
|
||||
78 POP_TOP
|
||||
79 POP_TOP
|
||||
80 JUMP_FORWARD (84)
|
||||
81 COPY (3)
|
||||
82 POP_EXCEPT
|
||||
83 RERAISE (1)
|
||||
>> 84 JUMP_FORWARD (110)
|
||||
85 PUSH_EXC_INFO
|
||||
52 LOAD_CONST (None)
|
||||
53 LOAD_CONST (None)
|
||||
54 CALL (3)
|
||||
55 GET_AWAITABLE
|
||||
56 LOAD_CONST (None)
|
||||
>> 57 SEND (62)
|
||||
58 YIELD_VALUE (1)
|
||||
59 RESUME (3)
|
||||
60 JUMP_BACKWARD_NO_INTERRUPT(57)
|
||||
61 CLEANUP_THROW
|
||||
>> 62 END_SEND
|
||||
63 POP_TOP
|
||||
64 JUMP_FORWARD (86)
|
||||
65 PUSH_EXC_INFO
|
||||
66 WITH_EXCEPT_START
|
||||
67 GET_AWAITABLE
|
||||
68 LOAD_CONST (None)
|
||||
>> 69 SEND (74)
|
||||
70 YIELD_VALUE (1)
|
||||
71 RESUME (3)
|
||||
72 JUMP_BACKWARD_NO_INTERRUPT(69)
|
||||
73 CLEANUP_THROW
|
||||
>> 74 END_SEND
|
||||
75 TO_BOOL
|
||||
76 POP_JUMP_IF_TRUE (78)
|
||||
77 RERAISE (2)
|
||||
>> 78 POP_TOP
|
||||
79 POP_EXCEPT
|
||||
80 POP_TOP
|
||||
81 POP_TOP
|
||||
82 JUMP_FORWARD (86)
|
||||
83 COPY (3)
|
||||
84 POP_EXCEPT
|
||||
85 RERAISE (1)
|
||||
>> 86 JUMP_FORWARD (112)
|
||||
87 PUSH_EXC_INFO
|
||||
|
||||
7 86 LOAD_GLOBAL (6, Exception)
|
||||
87 CHECK_EXC_MATCH
|
||||
88 POP_JUMP_IF_FALSE (106)
|
||||
89 STORE_FAST (1, ex)
|
||||
7 88 LOAD_GLOBAL (6, Exception)
|
||||
89 CHECK_EXC_MATCH
|
||||
90 POP_JUMP_IF_FALSE (108)
|
||||
91 STORE_FAST (1, ex)
|
||||
|
||||
8 90 LOAD_GLOBAL (2, self)
|
||||
91 LOAD_ATTR (15, assertIs, method=true)
|
||||
92 LOAD_FAST (1, ex)
|
||||
93 LOAD_FAST (0, stop_exc)
|
||||
94 CALL (2)
|
||||
95 POP_TOP
|
||||
96 JUMP_FORWARD (101)
|
||||
97 LOAD_CONST (None)
|
||||
98 STORE_FAST (1, ex)
|
||||
99 DELETE_FAST (1, ex)
|
||||
100 RAISE_VARARGS (ReraiseFromStack)
|
||||
>> 101 POP_EXCEPT
|
||||
102 LOAD_CONST (None)
|
||||
103 STORE_FAST (1, ex)
|
||||
104 DELETE_FAST (1, ex)
|
||||
105 JUMP_FORWARD (118)
|
||||
>> 106 RAISE_VARARGS (ReraiseFromStack)
|
||||
107 COPY (3)
|
||||
108 POP_EXCEPT
|
||||
109 RAISE_VARARGS (ReraiseFromStack)
|
||||
8 92 LOAD_GLOBAL (2, self)
|
||||
93 LOAD_ATTR (15, assertIs, method=true)
|
||||
94 LOAD_FAST (1, ex)
|
||||
95 LOAD_FAST (0, stop_exc)
|
||||
96 CALL (2)
|
||||
97 POP_TOP
|
||||
98 JUMP_FORWARD (103)
|
||||
99 LOAD_CONST (None)
|
||||
100 STORE_FAST (1, ex)
|
||||
101 DELETE_FAST (1, ex)
|
||||
102 RAISE_VARARGS (ReraiseFromStack)
|
||||
>> 103 POP_EXCEPT
|
||||
104 LOAD_CONST (None)
|
||||
105 STORE_FAST (1, ex)
|
||||
106 DELETE_FAST (1, ex)
|
||||
107 JUMP_FORWARD (120)
|
||||
>> 108 RAISE_VARARGS (ReraiseFromStack)
|
||||
109 COPY (3)
|
||||
110 POP_EXCEPT
|
||||
111 RAISE_VARARGS (ReraiseFromStack)
|
||||
|
||||
10 >> 110 LOAD_GLOBAL (2, self)
|
||||
111 LOAD_ATTR (17, fail, method=true)
|
||||
112 LOAD_FAST (0, stop_exc)
|
||||
113 FORMAT_SIMPLE
|
||||
114 LOAD_CONST (" was suppressed")
|
||||
115 BUILD_STRING (2)
|
||||
116 CALL (1)
|
||||
117 POP_TOP
|
||||
10 >> 112 LOAD_GLOBAL (2, self)
|
||||
113 LOAD_ATTR (17, fail, method=true)
|
||||
114 LOAD_FAST (0, stop_exc)
|
||||
115 FORMAT_SIMPLE
|
||||
116 LOAD_CONST (" was suppressed")
|
||||
117 BUILD_STRING (2)
|
||||
118 CALL (1)
|
||||
119 POP_TOP
|
||||
|
||||
3 >> 118 PUSH_NULL
|
||||
119 LOAD_CONST (None)
|
||||
120 LOAD_CONST (None)
|
||||
3 >> 120 PUSH_NULL
|
||||
121 LOAD_CONST (None)
|
||||
122 CALL (3)
|
||||
123 POP_TOP
|
||||
124 JUMP_FORWARD (138)
|
||||
125 PUSH_EXC_INFO
|
||||
126 WITH_EXCEPT_START
|
||||
127 TO_BOOL
|
||||
128 POP_JUMP_IF_TRUE (130)
|
||||
129 RERAISE (2)
|
||||
>> 130 POP_TOP
|
||||
131 POP_EXCEPT
|
||||
132 POP_TOP
|
||||
133 POP_TOP
|
||||
134 JUMP_FORWARD (138)
|
||||
135 COPY (3)
|
||||
136 POP_EXCEPT
|
||||
137 RERAISE (1)
|
||||
>> 138 JUMP_BACKWARD (11)
|
||||
>> 139 POP_TOP
|
||||
140 LOAD_CONST (None)
|
||||
141 RETURN_VALUE
|
||||
122 LOAD_CONST (None)
|
||||
123 LOAD_CONST (None)
|
||||
124 CALL (3)
|
||||
125 POP_TOP
|
||||
126 JUMP_FORWARD (140)
|
||||
127 PUSH_EXC_INFO
|
||||
128 WITH_EXCEPT_START
|
||||
129 TO_BOOL
|
||||
130 POP_JUMP_IF_TRUE (132)
|
||||
131 RERAISE (2)
|
||||
>> 132 POP_TOP
|
||||
133 POP_EXCEPT
|
||||
134 POP_TOP
|
||||
135 POP_TOP
|
||||
136 JUMP_FORWARD (140)
|
||||
137 COPY (3)
|
||||
138 POP_EXCEPT
|
||||
139 RERAISE (1)
|
||||
>> 140 JUMP_BACKWARD (13)
|
||||
>> 141 END_FOR
|
||||
142 POP_ITER
|
||||
143 LOAD_CONST (None)
|
||||
144 RETURN_VALUE
|
||||
|
||||
1 MAKE_FUNCTION
|
||||
2 STORE_NAME (0, test)
|
||||
|
||||
@@ -845,7 +845,13 @@ impl SymbolTableBuilder {
|
||||
let is_nested = self
|
||||
.tables
|
||||
.last()
|
||||
.map(|table| table.is_nested || table.typ == CompilerScope::Function)
|
||||
.map(|table| {
|
||||
table.is_nested
|
||||
|| matches!(
|
||||
table.typ,
|
||||
CompilerScope::Function | CompilerScope::AsyncFunction
|
||||
)
|
||||
})
|
||||
.unwrap_or(false);
|
||||
let table = SymbolTable::new(name.to_owned(), typ, line_number, is_nested);
|
||||
self.tables.push(table);
|
||||
@@ -1103,6 +1109,7 @@ impl SymbolTableBuilder {
|
||||
type_params,
|
||||
returns,
|
||||
range,
|
||||
is_async,
|
||||
..
|
||||
}) => {
|
||||
self.scan_decorators(decorator_list, ExpressionContext::Load)?;
|
||||
@@ -1142,6 +1149,7 @@ impl SymbolTableBuilder {
|
||||
parameters,
|
||||
self.line_index_start(*range),
|
||||
has_return_annotation,
|
||||
*is_async,
|
||||
)?;
|
||||
self.scan_statements(body)?;
|
||||
self.leave_scope();
|
||||
@@ -1709,7 +1717,10 @@ impl SymbolTableBuilder {
|
||||
// Interesting stuff about the __class__ variable:
|
||||
// https://docs.python.org/3/reference/datamodel.html?highlight=__class__#creating-the-class-object
|
||||
if context == ExpressionContext::Load
|
||||
&& self.tables.last().unwrap().typ == CompilerScope::Function
|
||||
&& matches!(
|
||||
self.tables.last().unwrap().typ,
|
||||
CompilerScope::Function | CompilerScope::AsyncFunction
|
||||
)
|
||||
&& id == "super"
|
||||
{
|
||||
self.register_name("__class__", SymbolUsage::Used, *range)?;
|
||||
@@ -1727,6 +1738,7 @@ impl SymbolTableBuilder {
|
||||
parameters,
|
||||
self.line_index_start(expression.range()),
|
||||
false, // lambdas have no return annotation
|
||||
false, // lambdas are never async
|
||||
)?;
|
||||
} else {
|
||||
self.enter_scope(
|
||||
@@ -2093,6 +2105,7 @@ impl SymbolTableBuilder {
|
||||
parameters: &ast::Parameters,
|
||||
line_number: u32,
|
||||
has_return_annotation: bool,
|
||||
is_async: bool,
|
||||
) -> SymbolTableResult {
|
||||
// Evaluate eventual default parameters:
|
||||
for default in parameters
|
||||
@@ -2157,7 +2170,12 @@ impl SymbolTableBuilder {
|
||||
None
|
||||
};
|
||||
|
||||
self.enter_scope(name, CompilerScope::Function, line_number);
|
||||
let scope_type = if is_async {
|
||||
CompilerScope::AsyncFunction
|
||||
} else {
|
||||
CompilerScope::Function
|
||||
};
|
||||
self.enter_scope(name, scope_type, line_number);
|
||||
|
||||
// Move annotation_block to function scope only if we have one
|
||||
if let Some(block) = annotation_block {
|
||||
|
||||
@@ -31,7 +31,7 @@ pub enum Instruction {
|
||||
CheckExcMatch = 6,
|
||||
CleanupThrow = 7,
|
||||
DeleteSubscr = 8,
|
||||
EndFor = 9, // Placeholder
|
||||
EndFor = 9,
|
||||
EndSend = 10,
|
||||
ExitInitCheck = 11, // Placeholder
|
||||
FormatSimple = 12,
|
||||
@@ -52,11 +52,11 @@ pub enum Instruction {
|
||||
Nop = 27,
|
||||
NotTaken = 28, // Placeholder
|
||||
PopExcept = 29,
|
||||
PopIter = 30, // Placeholder
|
||||
PopIter = 30,
|
||||
PopTop = 31,
|
||||
PushExcInfo = 32,
|
||||
PushNull = 33,
|
||||
ReturnGenerator = 34, // Placeholder
|
||||
ReturnGenerator = 34,
|
||||
ReturnValue = 35,
|
||||
SetupAnnotations = 36,
|
||||
StoreSlice = 37, // Placeholder
|
||||
@@ -190,7 +190,7 @@ pub enum Instruction {
|
||||
LoadName(Arg<NameIdx>) = 93,
|
||||
LoadSmallInt {
|
||||
idx: Arg<u32>,
|
||||
} = 94, // Placeholder
|
||||
} = 94,
|
||||
LoadSpecial {
|
||||
method: Arg<SpecialMethod>,
|
||||
} = 95,
|
||||
@@ -269,12 +269,12 @@ pub enum Instruction {
|
||||
BinaryOpExtend = 132, // Placeholder
|
||||
BinaryOpMultiplyFloat = 133, // Placeholder
|
||||
BinaryOpMultiplyInt = 134, // Placeholder
|
||||
BinarySubscrDict = 135, // Placeholder
|
||||
BinarySubscrGetitem = 136, // Placeholder
|
||||
BinarySubscrListInt = 137, // Placeholder
|
||||
BinarySubscrListSlice = 138, // Placeholder
|
||||
BinarySubscrStrInt = 139, // Placeholder
|
||||
BinarySubscrTupleInt = 140, // Placeholder
|
||||
BinaryOpSubscrDict = 135, // Placeholder
|
||||
BinaryOpSubscrGetitem = 136, // Placeholder
|
||||
BinaryOpSubscrListInt = 137, // Placeholder
|
||||
BinaryOpSubscrListSlice = 138, // Placeholder
|
||||
BinaryOpSubscrStrInt = 139, // Placeholder
|
||||
BinaryOpSubscrTupleInt = 140, // Placeholder
|
||||
BinaryOpSubtractFloat = 141, // Placeholder
|
||||
BinaryOpSubtractInt = 142, // Placeholder
|
||||
CallAllocAndEnterInit = 143, // Placeholder
|
||||
@@ -444,7 +444,7 @@ impl InstructionMetadata for Instruction {
|
||||
)
|
||||
}
|
||||
|
||||
fn stack_effect(&self, arg: OpArg, jump: bool) -> i32 {
|
||||
fn stack_effect(&self, arg: OpArg) -> i32 {
|
||||
match self {
|
||||
Self::Nop => 0,
|
||||
Self::NotTaken => 0,
|
||||
@@ -506,11 +506,7 @@ impl InstructionMetadata for Instruction {
|
||||
Self::ConvertValue { .. } => 0,
|
||||
Self::FormatSimple => 0,
|
||||
Self::FormatWithSpec => -1,
|
||||
Self::ForIter { .. } => {
|
||||
// jump=False: push next value (+1)
|
||||
// jump=True: iterator stays on stack, no change (0)
|
||||
if jump { 0 } else { 1 }
|
||||
}
|
||||
Self::ForIter { .. } => 1, // push next value
|
||||
Self::IsOp(_) => -1,
|
||||
Self::ContainsOp(_) => -1,
|
||||
Self::ReturnValue => -1,
|
||||
@@ -571,7 +567,7 @@ impl InstructionMetadata for Instruction {
|
||||
let UnpackExArgs { before, after } = args.get(arg);
|
||||
-1 + before as i32 + 1 + after as i32
|
||||
}
|
||||
Self::PopExcept => 0,
|
||||
Self::PopExcept => -1,
|
||||
Self::PopIter => -1,
|
||||
Self::GetAwaitable => 0,
|
||||
Self::GetAIter => 0,
|
||||
@@ -595,14 +591,14 @@ impl InstructionMetadata for Instruction {
|
||||
}
|
||||
// Pseudo instructions (calculated before conversion)
|
||||
Self::Cache => 0,
|
||||
Self::BinarySlice => 0,
|
||||
Self::BinarySlice => -2, // (container, start, stop -- res)
|
||||
Self::BinaryOpInplaceAddUnicode => 0,
|
||||
Self::EndFor => 0,
|
||||
Self::ExitInitCheck => 0,
|
||||
Self::EndFor => -1, // pop next value at end of loop iteration
|
||||
Self::ExitInitCheck => -1, // (should_be_none -- )
|
||||
Self::InterpreterExit => 0,
|
||||
Self::LoadLocals => 0,
|
||||
Self::ReturnGenerator => 0,
|
||||
Self::StoreSlice => 0,
|
||||
Self::LoadLocals => 1, // ( -- locals)
|
||||
Self::ReturnGenerator => 1, // pushes None for POP_TOP to consume
|
||||
Self::StoreSlice => -4, // (v, container, start, stop -- )
|
||||
Self::CopyFreeVars { .. } => 0,
|
||||
Self::EnterExecutor => 0,
|
||||
Self::JumpBackwardNoInterrupt { .. } => 0,
|
||||
@@ -614,8 +610,8 @@ impl InstructionMetadata for Instruction {
|
||||
Self::LoadFromDictOrGlobals(_) => 0,
|
||||
Self::MakeCell(_) => 0,
|
||||
Self::StoreFastStoreFast { .. } => 0,
|
||||
Self::PopJumpIfNone { .. } => 0,
|
||||
Self::PopJumpIfNotNone { .. } => 0,
|
||||
Self::PopJumpIfNone { .. } => -1, // (value -- )
|
||||
Self::PopJumpIfNotNone { .. } => -1, // (value -- )
|
||||
Self::BinaryOpAddFloat => 0,
|
||||
Self::BinaryOpAddInt => 0,
|
||||
Self::BinaryOpAddUnicode => 0,
|
||||
@@ -624,12 +620,12 @@ impl InstructionMetadata for Instruction {
|
||||
Self::BinaryOpMultiplyInt => 0,
|
||||
Self::BinaryOpSubtractFloat => 0,
|
||||
Self::BinaryOpSubtractInt => 0,
|
||||
Self::BinarySubscrDict => 0,
|
||||
Self::BinarySubscrGetitem => 0,
|
||||
Self::BinarySubscrListInt => 0,
|
||||
Self::BinarySubscrListSlice => 0,
|
||||
Self::BinarySubscrStrInt => 0,
|
||||
Self::BinarySubscrTupleInt => 0,
|
||||
Self::BinaryOpSubscrDict => 0,
|
||||
Self::BinaryOpSubscrGetitem => 0,
|
||||
Self::BinaryOpSubscrListInt => 0,
|
||||
Self::BinaryOpSubscrListSlice => 0,
|
||||
Self::BinaryOpSubscrStrInt => 0,
|
||||
Self::BinaryOpSubscrTupleInt => 0,
|
||||
Self::CallAllocAndEnterInit => 0,
|
||||
Self::CallBoundMethodExactArgs => 0,
|
||||
Self::CallBoundMethodGeneral => 0,
|
||||
@@ -848,6 +844,7 @@ impl InstructionMetadata for Instruction {
|
||||
Self::LoadBuildClass => w!(LOAD_BUILD_CLASS),
|
||||
Self::LoadFromDictOrDeref(i) => w!(LOAD_FROM_DICT_OR_DEREF, cell_name = i),
|
||||
Self::LoadConst { idx } => fmt_const("LOAD_CONST", arg, f, idx),
|
||||
Self::LoadSmallInt { idx } => w!(LOAD_SMALL_INT, idx),
|
||||
Self::LoadDeref(idx) => w!(LOAD_DEREF, cell_name = idx),
|
||||
Self::LoadFast(idx) => w!(LOAD_FAST, varname = idx),
|
||||
Self::LoadFastAndClear(idx) => w!(LOAD_FAST_AND_CLEAR, varname = idx),
|
||||
@@ -875,6 +872,8 @@ impl InstructionMetadata for Instruction {
|
||||
Self::PopJumpIfFalse { target } => w!(POP_JUMP_IF_FALSE, target),
|
||||
Self::PopJumpIfTrue { target } => w!(POP_JUMP_IF_TRUE, target),
|
||||
Self::PopTop => w!(POP_TOP),
|
||||
Self::EndFor => w!(END_FOR),
|
||||
Self::PopIter => w!(POP_ITER),
|
||||
Self::PushExcInfo => w!(PUSH_EXC_INFO),
|
||||
Self::PushNull => w!(PUSH_NULL),
|
||||
Self::RaiseVarargs { kind } => w!(RAISE_VARARGS, ?kind),
|
||||
@@ -917,37 +916,48 @@ impl InstructionMetadata for Instruction {
|
||||
}
|
||||
|
||||
/// Instructions used by the compiler. They are not executed by the VM.
|
||||
///
|
||||
/// CPython 3.14.2 aligned (256-266), RustPython-specific variants start at 267.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(u16)]
|
||||
pub enum PseudoInstruction {
|
||||
// CPython 3.14.2 pseudo instructions (256-266)
|
||||
AnnotationsPlaceholder = 256,
|
||||
Jump {
|
||||
target: Arg<Label>,
|
||||
} = 256,
|
||||
} = 257,
|
||||
JumpIfFalse {
|
||||
target: Arg<Label>,
|
||||
} = 258,
|
||||
JumpIfTrue {
|
||||
target: Arg<Label>,
|
||||
} = 259,
|
||||
JumpNoInterrupt {
|
||||
target: Arg<Label>,
|
||||
} = 257, // Placeholder
|
||||
Reserved258 = 258,
|
||||
} = 260,
|
||||
LoadClosure(Arg<NameIdx>) = 261,
|
||||
PopBlock = 262,
|
||||
SetupCleanup = 263,
|
||||
SetupFinally = 264,
|
||||
SetupWith = 265,
|
||||
StoreFastMaybeNull(Arg<NameIdx>) = 266,
|
||||
|
||||
// RustPython-specific pseudo instructions (267+)
|
||||
LoadAttrMethod {
|
||||
idx: Arg<NameIdx>,
|
||||
} = 259,
|
||||
} = 267,
|
||||
// "Zero" variants are for 0-arg super() calls (has_class=false).
|
||||
// Non-"Zero" variants are for 2-arg super(cls, self) calls (has_class=true).
|
||||
/// 2-arg super(cls, self).method() - has_class=true, load_method=true
|
||||
LoadSuperMethod {
|
||||
idx: Arg<NameIdx>,
|
||||
} = 260,
|
||||
} = 268,
|
||||
LoadZeroSuperAttr {
|
||||
idx: Arg<NameIdx>,
|
||||
} = 261,
|
||||
} = 269,
|
||||
LoadZeroSuperMethod {
|
||||
idx: Arg<NameIdx>,
|
||||
} = 262,
|
||||
PopBlock = 263,
|
||||
SetupCleanup = 264, // Placeholder
|
||||
SetupFinally = 265, // Placeholder
|
||||
SetupWith = 266, // Placeholder
|
||||
StoreFastMaybeNull = 267, // Placeholder
|
||||
LoadClosure(Arg<NameIdx>) = 268,
|
||||
} = 270,
|
||||
}
|
||||
|
||||
const _: () = assert!(mem::size_of::<PseudoInstruction>() == 2);
|
||||
@@ -965,10 +975,8 @@ impl TryFrom<u16> for PseudoInstruction {
|
||||
|
||||
#[inline]
|
||||
fn try_from(value: u16) -> Result<Self, MarshalError> {
|
||||
let start = u16::from(Self::Jump {
|
||||
target: Arg::marker(),
|
||||
});
|
||||
let end = u16::from(Self::LoadClosure(Arg::marker()));
|
||||
let start = u16::from(Self::AnnotationsPlaceholder);
|
||||
let end = u16::from(Self::LoadZeroSuperMethod { idx: Arg::marker() });
|
||||
|
||||
if (start..=end).contains(&value) {
|
||||
Ok(unsafe { mem::transmute::<u16, Self>(value) })
|
||||
@@ -981,30 +989,35 @@ impl TryFrom<u16> for PseudoInstruction {
|
||||
impl InstructionMetadata for PseudoInstruction {
|
||||
fn label_arg(&self) -> Option<Arg<Label>> {
|
||||
match self {
|
||||
Self::Jump { target: l } => Some(*l),
|
||||
Self::Jump { target: l }
|
||||
| Self::JumpIfFalse { target: l }
|
||||
| Self::JumpIfTrue { target: l }
|
||||
| Self::JumpNoInterrupt { target: l } => Some(*l),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn unconditional_branch(&self) -> bool {
|
||||
matches!(self, Self::Jump { .. },)
|
||||
matches!(self, Self::Jump { .. } | Self::JumpNoInterrupt { .. })
|
||||
}
|
||||
|
||||
fn stack_effect(&self, _arg: OpArg, _jump: bool) -> i32 {
|
||||
fn stack_effect(&self, _arg: OpArg) -> i32 {
|
||||
match self {
|
||||
Self::AnnotationsPlaceholder => 0,
|
||||
Self::Jump { .. } => 0,
|
||||
Self::JumpIfFalse { .. } => 0, // peek, don't pop: COPY + TO_BOOL + POP_JUMP_IF_FALSE
|
||||
Self::JumpIfTrue { .. } => 0, // peek, don't pop: COPY + TO_BOOL + POP_JUMP_IF_TRUE
|
||||
Self::JumpNoInterrupt { .. } => 0,
|
||||
Self::LoadAttrMethod { .. } => 1, // pop obj, push method + self_or_null
|
||||
Self::LoadSuperMethod { .. } => -3 + 2, // pop 3, push [method, self_or_null]
|
||||
Self::LoadZeroSuperAttr { .. } => -3 + 1, // pop 3, push [attr]
|
||||
Self::LoadZeroSuperMethod { .. } => -3 + 2, // pop 3, push [method, self_or_null]
|
||||
Self::LoadClosure(_) => 1,
|
||||
Self::PopBlock => 0,
|
||||
Self::SetupCleanup => 0,
|
||||
Self::SetupFinally => 0,
|
||||
Self::SetupWith => 0,
|
||||
Self::StoreFastMaybeNull => 0,
|
||||
Self::Reserved258 => 0,
|
||||
Self::LoadClosure(_) => 1,
|
||||
Self::StoreFastMaybeNull(_) => -1,
|
||||
Self::LoadAttrMethod { .. } => 1, // pop obj, push method + self_or_null
|
||||
Self::LoadSuperMethod { .. } => -3 + 2, // pop 3, push [method, self_or_null]
|
||||
Self::LoadZeroSuperAttr { .. } => -3 + 1, // pop 3, push [attr]
|
||||
Self::LoadZeroSuperMethod { .. } => -3 + 2, // pop 3, push [method, self_or_null]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1074,7 +1087,7 @@ impl InstructionMetadata for AnyInstruction {
|
||||
|
||||
inst_either!(fn unconditional_branch(&self) -> bool);
|
||||
|
||||
inst_either!(fn stack_effect(&self, arg: OpArg, jump: bool) -> i32);
|
||||
inst_either!(fn stack_effect(&self, arg: OpArg) -> i32);
|
||||
|
||||
inst_either!(fn fmt_dis(
|
||||
&self,
|
||||
@@ -1148,9 +1161,9 @@ pub trait InstructionMetadata {
|
||||
/// use rustpython_compiler_core::bytecode::{Arg, Instruction, Label, InstructionMetadata};
|
||||
/// let (target, jump_arg) = Arg::new(Label(0xF));
|
||||
/// let jump_instruction = Instruction::JumpForward { target };
|
||||
/// assert_eq!(jump_instruction.stack_effect(jump_arg, true), 0);
|
||||
/// assert_eq!(jump_instruction.stack_effect(jump_arg), 0);
|
||||
/// ```
|
||||
fn stack_effect(&self, arg: OpArg, jump: bool) -> i32;
|
||||
fn stack_effect(&self, arg: OpArg) -> i32;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn fmt_dis(
|
||||
|
||||
@@ -290,8 +290,9 @@ op_arg_enum!(
|
||||
Print = 1,
|
||||
/// Import * operation
|
||||
ImportStar = 2,
|
||||
// StopIterationError = 3,
|
||||
// AsyncGenWrap = 4,
|
||||
/// Convert StopIteration to RuntimeError in async context
|
||||
StopIterationError = 3,
|
||||
AsyncGenWrap = 4,
|
||||
UnaryPositive = 5,
|
||||
/// Convert list to tuple
|
||||
ListToTuple = 6,
|
||||
|
||||
@@ -572,6 +572,12 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> {
|
||||
self.stack.push(val);
|
||||
Ok(())
|
||||
}
|
||||
Instruction::LoadSmallInt { idx } => {
|
||||
let small_int = idx.get(arg) as i64;
|
||||
let val = self.builder.ins().iconst(types::I64, small_int);
|
||||
self.stack.push(JitValue::Int(val));
|
||||
Ok(())
|
||||
}
|
||||
Instruction::LoadFast(idx) => {
|
||||
let local = self.variables[idx.get(arg) as usize]
|
||||
.as_ref()
|
||||
|
||||
@@ -194,7 +194,8 @@ mod _opcode {
|
||||
|
||||
let opcode = Opcode::try_from_pyint(args.opcode, vm)?;
|
||||
|
||||
Ok(opcode.stack_effect(oparg.into(), jump))
|
||||
let _ = jump; // Python API accepts jump but it's not used
|
||||
Ok(opcode.stack_effect(oparg.into()))
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
|
||||
+100
-3
@@ -1134,6 +1134,12 @@ impl ExecutingFrame<'_> {
|
||||
self.push_value(vm.builtins.get_attr(identifier!(vm, __build_class__), vm)?);
|
||||
Ok(None)
|
||||
}
|
||||
Instruction::LoadLocals => {
|
||||
// Push the locals dict onto the stack
|
||||
let locals = self.locals.clone().into_object();
|
||||
self.push_value(locals);
|
||||
Ok(None)
|
||||
}
|
||||
Instruction::LoadFromDictOrDeref(i) => {
|
||||
let i = i.get(arg) as usize;
|
||||
let name = if i < self.code.cellvars.len() {
|
||||
@@ -1177,6 +1183,12 @@ impl ExecutingFrame<'_> {
|
||||
self.push_value(self.code.constants[idx.get(arg) as usize].clone().into());
|
||||
Ok(None)
|
||||
}
|
||||
Instruction::LoadSmallInt { idx } => {
|
||||
// Push small integer (-5..=256) directly without constant table lookup
|
||||
let value = vm.ctx.new_int(idx.get(arg) as i32);
|
||||
self.push_value(value.into());
|
||||
Ok(None)
|
||||
}
|
||||
Instruction::LoadDeref(i) => {
|
||||
let idx = i.get(arg) as usize;
|
||||
let x = self.cells_frees[idx]
|
||||
@@ -1487,6 +1499,15 @@ impl ExecutingFrame<'_> {
|
||||
Ok(None)
|
||||
}
|
||||
Instruction::Nop => Ok(None),
|
||||
Instruction::ReturnGenerator => {
|
||||
// In RustPython, generators/coroutines are created in function.rs
|
||||
// before the frame starts executing. The RETURN_GENERATOR instruction
|
||||
// pushes None so that the following POP_TOP has something to consume.
|
||||
// This matches CPython's semantics where the sent value (None for first call)
|
||||
// is on the stack when the generator resumes.
|
||||
self.push_value(vm.ctx.none());
|
||||
Ok(None)
|
||||
}
|
||||
Instruction::PopExcept => {
|
||||
// Pop prev_exc from value stack and restore it
|
||||
let prev_exc = self.pop_value();
|
||||
@@ -1507,11 +1528,35 @@ impl ExecutingFrame<'_> {
|
||||
}
|
||||
Instruction::PopJumpIfFalse { target } => self.pop_jump_if(vm, target.get(arg), false),
|
||||
Instruction::PopJumpIfTrue { target } => self.pop_jump_if(vm, target.get(arg), true),
|
||||
Instruction::PopJumpIfNone { target } => {
|
||||
let value = self.pop_value();
|
||||
if vm.is_none(&value) {
|
||||
self.jump(target.get(arg));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
Instruction::PopJumpIfNotNone { target } => {
|
||||
let value = self.pop_value();
|
||||
if !vm.is_none(&value) {
|
||||
self.jump(target.get(arg));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
Instruction::PopTop => {
|
||||
// Pop value from stack and ignore.
|
||||
self.pop_value();
|
||||
Ok(None)
|
||||
}
|
||||
Instruction::EndFor => {
|
||||
// Pop the next value from stack (cleanup after loop body)
|
||||
self.pop_value();
|
||||
Ok(None)
|
||||
}
|
||||
Instruction::PopIter => {
|
||||
// Pop the iterator from stack (end of for loop)
|
||||
self.pop_value();
|
||||
Ok(None)
|
||||
}
|
||||
Instruction::PushNull => {
|
||||
// Push NULL for self_or_null slot in call protocol
|
||||
self.push_null();
|
||||
@@ -1765,6 +1810,17 @@ impl ExecutingFrame<'_> {
|
||||
self.push_value(value);
|
||||
Ok(None)
|
||||
}
|
||||
Instruction::ExitInitCheck => {
|
||||
// Check that __init__ returned None
|
||||
let should_be_none = self.pop_value();
|
||||
if !vm.is_none(&should_be_none) {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"__init__() should return None, not '{}'",
|
||||
should_be_none.class().name()
|
||||
)));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
Instruction::CleanupThrow => {
|
||||
// CLEANUP_THROW: (sub_iter, last_sent_val, exc) -> (None, value) OR re-raise
|
||||
// If StopIteration: pop all 3, extract value, push (None, value)
|
||||
@@ -2285,9 +2341,26 @@ impl ExecutingFrame<'_> {
|
||||
Ok(None)
|
||||
}
|
||||
Ok(PyIterReturn::StopIteration(_)) => {
|
||||
// CPython 3.14: Do NOT pop iterator here
|
||||
// POP_ITER instruction will handle cleanup after the loop
|
||||
self.jump(target);
|
||||
// Check if target instruction is END_FOR (CPython 3.14 pattern)
|
||||
// If so, skip it and jump to target + 1 instruction (POP_ITER)
|
||||
let target_idx = target.0 as usize;
|
||||
let jump_target = if let Some(unit) = self.code.instructions.get(target_idx) {
|
||||
if matches!(unit.op, bytecode::Instruction::EndFor)
|
||||
&& matches!(
|
||||
self.code.instructions.get(target_idx + 1).map(|u| &u.op),
|
||||
Some(bytecode::Instruction::PopIter)
|
||||
)
|
||||
{
|
||||
// Skip END_FOR, jump to POP_ITER
|
||||
bytecode::Label(target.0 + 1)
|
||||
} else {
|
||||
// Legacy pattern: jump directly to target (POP_TOP/POP_ITER)
|
||||
target
|
||||
}
|
||||
} else {
|
||||
target
|
||||
};
|
||||
self.jump(jump_target);
|
||||
Ok(None)
|
||||
}
|
||||
Err(next_error) => {
|
||||
@@ -2684,6 +2757,30 @@ impl ExecutingFrame<'_> {
|
||||
.map_err(|_| vm.new_type_error("LIST_TO_TUPLE expects a list"))?;
|
||||
Ok(vm.ctx.new_tuple(list.borrow_vec().to_vec()).into())
|
||||
}
|
||||
bytecode::IntrinsicFunction1::StopIterationError => {
|
||||
// Convert StopIteration to RuntimeError
|
||||
// Used to ensure async generators don't raise StopIteration directly
|
||||
// _PyGen_FetchStopIterationValue
|
||||
// Use fast_isinstance to handle subclasses of StopIteration
|
||||
if arg.fast_isinstance(vm.ctx.exceptions.stop_iteration) {
|
||||
Err(vm.new_runtime_error("coroutine raised StopIteration"))
|
||||
} else {
|
||||
// If not StopIteration, just re-raise the original exception
|
||||
Err(arg.downcast().unwrap_or_else(|obj| {
|
||||
vm.new_runtime_error(format!(
|
||||
"unexpected exception type: {:?}",
|
||||
obj.class()
|
||||
))
|
||||
}))
|
||||
}
|
||||
}
|
||||
bytecode::IntrinsicFunction1::AsyncGenWrap => {
|
||||
// Wrap value for async generator
|
||||
// Creates an AsyncGenWrappedValue
|
||||
Ok(crate::builtins::asyncgenerator::PyAsyncGenWrappedValue(arg)
|
||||
.into_ref(&vm.ctx)
|
||||
.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user