mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
Bytecode pseudo opcodes (#6715)
This commit is contained in:
1 parent
13a875f609
commit
e3890f9b4a
11 files changed
+652
-416
No files matched your search
Vendored
+12
-6
@@ -138,14 +138,20 @@ opmap = {
|
||||
'JUMP_IF_NOT_EXC_MATCH': 131,
|
||||
'SET_EXC_INFO': 134,
|
||||
'SUBSCRIPT': 135,
|
||||
'LOAD_SUPER_METHOD': 136,
|
||||
'LOAD_ZERO_SUPER_ATTR': 137,
|
||||
'LOAD_ZERO_SUPER_METHOD': 138,
|
||||
'RESUME': 149,
|
||||
'JUMP': 252,
|
||||
'LOAD_CLOSURE': 253,
|
||||
'LOAD_ATTR_METHOD': 254,
|
||||
'POP_BLOCK': 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,
|
||||
}
|
||||
|
||||
# CPython 3.13 compatible: opcodes < 44 have no argument
|
||||
|
||||
Vendored
-1
@@ -16,7 +16,6 @@ class OpListTests(unittest.TestCase):
|
||||
self.assertIsInstance(func(op), bool)
|
||||
self.assertEqual(func(op), expected)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; Only supporting u8 ATM
|
||||
def test_invalid_opcodes(self):
|
||||
invalid = [-100, -1, 255, 512, 513, 1000]
|
||||
self.check_bool_function_result(_opcode.is_valid, invalid, False)
|
||||
|
||||
@@ -34,16 +34,17 @@ use ruff_python_ast::{
|
||||
visitor::{Visitor, walk_expr},
|
||||
};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use std::collections::HashSet;
|
||||
|
||||
use rustpython_compiler_core::{
|
||||
Mode, OneIndexed, PositionEncoding, SourceFile, SourceLocation,
|
||||
bytecode::{
|
||||
self, Arg as OpArgMarker, BinaryOperator, BuildSliceArgCount, CodeObject,
|
||||
self, AnyInstruction, Arg as OpArgMarker, BinaryOperator, BuildSliceArgCount, CodeObject,
|
||||
ComparisonOperator, ConstantData, ConvertValueOparg, Instruction, Invert, OpArg, OpArgType,
|
||||
UnpackExArgs,
|
||||
PseudoInstruction, UnpackExArgs,
|
||||
},
|
||||
};
|
||||
use rustpython_wtf8::Wtf8Buf;
|
||||
use std::collections::HashSet;
|
||||
|
||||
const MAXBLOCKS: usize = 20;
|
||||
|
||||
@@ -274,17 +275,24 @@ pub fn compile_expression(
|
||||
}
|
||||
|
||||
macro_rules! emit {
|
||||
($c:expr, Instruction::$op:ident { $arg:ident$(,)? }$(,)?) => {
|
||||
$c.emit_arg($arg, |x| Instruction::$op { $arg: x })
|
||||
// Struct variant with single identifier (e.g., Foo::A { arg })
|
||||
($c:expr, $enum:ident :: $op:ident { $arg:ident $(,)? } $(,)?) => {
|
||||
$c.emit_arg($arg, |x| $enum::$op { $arg: x })
|
||||
};
|
||||
($c:expr, Instruction::$op:ident { $arg:ident : $arg_val:expr $(,)? }$(,)?) => {
|
||||
$c.emit_arg($arg_val, |x| Instruction::$op { $arg: x })
|
||||
|
||||
// Struct variant with explicit value (e.g., Foo::A { arg: 42 })
|
||||
($c:expr, $enum:ident :: $op:ident { $arg:ident : $arg_val:expr $(,)? } $(,)?) => {
|
||||
$c.emit_arg($arg_val, |x| $enum::$op { $arg: x })
|
||||
};
|
||||
($c:expr, Instruction::$op:ident( $arg_val:expr $(,)? )$(,)?) => {
|
||||
$c.emit_arg($arg_val, Instruction::$op)
|
||||
|
||||
// Tuple variant (e.g., Foo::B(42))
|
||||
($c:expr, $enum:ident :: $op:ident($arg_val:expr $(,)? ) $(,)?) => {
|
||||
$c.emit_arg($arg_val, $enum::$op)
|
||||
};
|
||||
($c:expr, Instruction::$op:ident$(,)?) => {
|
||||
$c.emit_no_arg(Instruction::$op)
|
||||
|
||||
// No-arg variant (e.g., Foo::C)
|
||||
($c:expr, $enum:ident :: $op:ident $(,)?) => {
|
||||
$c.emit_no_arg($enum::$op)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1181,7 +1189,7 @@ impl Compiler {
|
||||
// Stack when entering: [..., __exit__, return_value (if preserve_tos)]
|
||||
// Need to call __exit__(None, None, None)
|
||||
|
||||
emit!(self, Instruction::PopBlock);
|
||||
emit!(self, PseudoInstruction::PopBlock);
|
||||
|
||||
// If preserving return value, swap it below __exit__
|
||||
if preserve_tos {
|
||||
@@ -1906,7 +1914,7 @@ impl Compiler {
|
||||
self.compile_statements(body)?;
|
||||
emit!(
|
||||
self,
|
||||
Instruction::Jump {
|
||||
PseudoInstruction::Jump {
|
||||
target: after_block
|
||||
}
|
||||
);
|
||||
@@ -1922,7 +1930,7 @@ impl Compiler {
|
||||
self.compile_statements(&clause.body)?;
|
||||
emit!(
|
||||
self,
|
||||
Instruction::Jump {
|
||||
PseudoInstruction::Jump {
|
||||
target: after_block
|
||||
}
|
||||
);
|
||||
@@ -2510,7 +2518,7 @@ impl Compiler {
|
||||
}
|
||||
|
||||
// Jump to end (skip exception path blocks)
|
||||
emit!(self, Instruction::Jump { target: end_block });
|
||||
emit!(self, PseudoInstruction::Jump { target: end_block });
|
||||
|
||||
if let Some(finally_except) = finally_except_block {
|
||||
// Restore sub_tables for exception path compilation
|
||||
@@ -2583,7 +2591,7 @@ impl Compiler {
|
||||
self.compile_statements(body)?;
|
||||
self.pop_fblock(FBlockType::TryExcept);
|
||||
// No PopBlock emit - exception table handles this
|
||||
emit!(self, Instruction::Jump { target: else_block });
|
||||
emit!(self, PseudoInstruction::Jump { target: else_block });
|
||||
|
||||
// except handlers:
|
||||
self.switch_to_block(handler_block);
|
||||
@@ -2666,7 +2674,7 @@ impl Compiler {
|
||||
let handler_normal_exit = self.new_block();
|
||||
emit!(
|
||||
self,
|
||||
Instruction::Jump {
|
||||
PseudoInstruction::Jump {
|
||||
target: handler_normal_exit,
|
||||
}
|
||||
);
|
||||
@@ -2715,7 +2723,7 @@ impl Compiler {
|
||||
// Jump to finally block
|
||||
emit!(
|
||||
self,
|
||||
Instruction::Jump {
|
||||
PseudoInstruction::Jump {
|
||||
target: finally_block,
|
||||
}
|
||||
);
|
||||
@@ -2790,7 +2798,7 @@ impl Compiler {
|
||||
self.compile_statements(finalbody)?;
|
||||
// Jump to end_block to skip exception path blocks
|
||||
// This prevents fall-through to finally_except_block
|
||||
emit!(self, Instruction::Jump { target: end_block });
|
||||
emit!(self, PseudoInstruction::Jump { target: end_block });
|
||||
}
|
||||
|
||||
// finally (exception path)
|
||||
@@ -2916,7 +2924,7 @@ impl Compiler {
|
||||
)?;
|
||||
self.compile_statements(body)?;
|
||||
self.pop_fblock(FBlockType::TryExcept);
|
||||
emit!(self, Instruction::Jump { target: else_block });
|
||||
emit!(self, PseudoInstruction::Jump { target: else_block });
|
||||
|
||||
// Exception handler entry
|
||||
self.switch_to_block(handler_block);
|
||||
@@ -3029,7 +3037,7 @@ impl Compiler {
|
||||
}
|
||||
|
||||
// Jump to next handler
|
||||
emit!(self, Instruction::Jump { target: next_block });
|
||||
emit!(self, PseudoInstruction::Jump { target: next_block });
|
||||
|
||||
// Handler raised an exception (cleanup_end label)
|
||||
self.switch_to_block(handler_except_block);
|
||||
@@ -3057,7 +3065,7 @@ impl Compiler {
|
||||
|
||||
// JUMP except_with_error
|
||||
// We directly JUMP to next_block since no_match_block falls through to it
|
||||
emit!(self, Instruction::Jump { target: next_block });
|
||||
emit!(self, PseudoInstruction::Jump { target: next_block });
|
||||
|
||||
// No match - pop match (None)
|
||||
self.switch_to_block(no_match_block);
|
||||
@@ -3081,7 +3089,7 @@ impl Compiler {
|
||||
// Stack: [prev_exc, orig, list]
|
||||
emit!(
|
||||
self,
|
||||
Instruction::Jump {
|
||||
PseudoInstruction::Jump {
|
||||
target: reraise_star_block
|
||||
}
|
||||
);
|
||||
@@ -3134,7 +3142,7 @@ impl Compiler {
|
||||
self.pop_fblock(FBlockType::FinallyTry);
|
||||
}
|
||||
|
||||
emit!(self, Instruction::Jump { target: end_block });
|
||||
emit!(self, PseudoInstruction::Jump { target: end_block });
|
||||
|
||||
// Reraise the result
|
||||
self.switch_to_block(reraise_block);
|
||||
@@ -3175,7 +3183,7 @@ impl Compiler {
|
||||
self.pop_fblock(FBlockType::FinallyTry);
|
||||
}
|
||||
|
||||
emit!(self, Instruction::Jump { target: end_block });
|
||||
emit!(self, PseudoInstruction::Jump { target: end_block });
|
||||
|
||||
self.switch_to_block(end_block);
|
||||
if !finalbody.is_empty() {
|
||||
@@ -4017,7 +4025,7 @@ impl Compiler {
|
||||
self.ctx.loop_data = was_in_loop;
|
||||
emit!(
|
||||
self,
|
||||
Instruction::Jump {
|
||||
PseudoInstruction::Jump {
|
||||
target: while_block,
|
||||
}
|
||||
);
|
||||
@@ -4154,7 +4162,7 @@ impl Compiler {
|
||||
emit!(self, Instruction::PopTop); // Pop __exit__ result
|
||||
emit!(
|
||||
self,
|
||||
Instruction::Jump {
|
||||
PseudoInstruction::Jump {
|
||||
target: after_block
|
||||
}
|
||||
);
|
||||
@@ -4226,7 +4234,7 @@ impl Compiler {
|
||||
emit!(self, Instruction::PopTop); // pop lasti
|
||||
emit!(
|
||||
self,
|
||||
Instruction::Jump {
|
||||
PseudoInstruction::Jump {
|
||||
target: after_block
|
||||
}
|
||||
);
|
||||
@@ -4313,7 +4321,7 @@ impl Compiler {
|
||||
let was_in_loop = self.ctx.loop_data.replace((for_block, after_block));
|
||||
self.compile_statements(body)?;
|
||||
self.ctx.loop_data = was_in_loop;
|
||||
emit!(self, Instruction::Jump { target: for_block });
|
||||
emit!(self, PseudoInstruction::Jump { target: for_block });
|
||||
|
||||
self.switch_to_block(else_block);
|
||||
|
||||
@@ -4372,7 +4380,7 @@ impl Compiler {
|
||||
JumpOp::Jump => {
|
||||
emit!(
|
||||
self,
|
||||
Instruction::Jump {
|
||||
PseudoInstruction::Jump {
|
||||
target: pc.fail_pop[pops]
|
||||
}
|
||||
);
|
||||
@@ -5038,7 +5046,7 @@ impl Compiler {
|
||||
}
|
||||
}
|
||||
// Emit a jump to the common end label and reset any failure jump targets.
|
||||
emit!(self, Instruction::Jump { target: end });
|
||||
emit!(self, PseudoInstruction::Jump { target: end });
|
||||
self.emit_and_reset_fail_pop(pc)?;
|
||||
}
|
||||
|
||||
@@ -5274,7 +5282,7 @@ impl Compiler {
|
||||
}
|
||||
|
||||
self.compile_statements(&m.body)?;
|
||||
emit!(self, Instruction::Jump { target: end });
|
||||
emit!(self, PseudoInstruction::Jump { target: end });
|
||||
self.emit_and_reset_fail_pop(pattern_context)?;
|
||||
}
|
||||
|
||||
@@ -5387,7 +5395,7 @@ impl Compiler {
|
||||
self.compile_addcompare(last_op);
|
||||
|
||||
let end = self.new_block();
|
||||
emit!(self, Instruction::Jump { target: end });
|
||||
emit!(self, PseudoInstruction::Jump { target: end });
|
||||
|
||||
// early exit left us with stack: `rhs, comparison_result`. We need to clean up rhs.
|
||||
self.switch_to_block(cleanup);
|
||||
@@ -5829,7 +5837,7 @@ impl Compiler {
|
||||
);
|
||||
|
||||
// JUMP_NO_INTERRUPT send (regular JUMP in RustPython)
|
||||
emit!(self, Instruction::Jump { target: send_block });
|
||||
emit!(self, PseudoInstruction::Jump { target: send_block });
|
||||
|
||||
// fail: CLEANUP_THROW
|
||||
// Stack when exception: [receiver, yielded_value, exc]
|
||||
@@ -5906,7 +5914,7 @@ impl Compiler {
|
||||
emit!(self, Instruction::LoadSuperAttr { arg: idx });
|
||||
}
|
||||
SuperCallType::ZeroArg => {
|
||||
emit!(self, Instruction::LoadZeroSuperAttr { idx });
|
||||
emit!(self, PseudoInstruction::LoadZeroSuperAttr { idx });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -6094,9 +6102,12 @@ impl Compiler {
|
||||
}) => {
|
||||
self.compile_comprehension(
|
||||
"<listcomp>",
|
||||
Some(Instruction::BuildList {
|
||||
size: OpArgMarker::marker(),
|
||||
}),
|
||||
Some(
|
||||
Instruction::BuildList {
|
||||
size: OpArgMarker::marker(),
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
generators,
|
||||
&|compiler| {
|
||||
compiler.compile_comprehension_element(elt)?;
|
||||
@@ -6117,9 +6128,12 @@ impl Compiler {
|
||||
}) => {
|
||||
self.compile_comprehension(
|
||||
"<setcomp>",
|
||||
Some(Instruction::BuildSet {
|
||||
size: OpArgMarker::marker(),
|
||||
}),
|
||||
Some(
|
||||
Instruction::BuildSet {
|
||||
size: OpArgMarker::marker(),
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
generators,
|
||||
&|compiler| {
|
||||
compiler.compile_comprehension_element(elt)?;
|
||||
@@ -6143,9 +6157,12 @@ impl Compiler {
|
||||
}) => {
|
||||
self.compile_comprehension(
|
||||
"<dictcomp>",
|
||||
Some(Instruction::BuildMap {
|
||||
size: OpArgMarker::marker(),
|
||||
}),
|
||||
Some(
|
||||
Instruction::BuildMap {
|
||||
size: OpArgMarker::marker(),
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
generators,
|
||||
&|compiler| {
|
||||
// changed evaluation order for Py38 named expression PEP 572
|
||||
@@ -6222,7 +6239,7 @@ impl Compiler {
|
||||
self.compile_expression(body)?;
|
||||
emit!(
|
||||
self,
|
||||
Instruction::Jump {
|
||||
PseudoInstruction::Jump {
|
||||
target: after_block,
|
||||
}
|
||||
);
|
||||
@@ -6347,10 +6364,10 @@ impl Compiler {
|
||||
let idx = self.name(attr.as_str());
|
||||
match super_type {
|
||||
SuperCallType::TwoArg { .. } => {
|
||||
emit!(self, Instruction::LoadSuperMethod { idx });
|
||||
emit!(self, PseudoInstruction::LoadSuperMethod { idx });
|
||||
}
|
||||
SuperCallType::ZeroArg => {
|
||||
emit!(self, Instruction::LoadZeroSuperMethod { idx });
|
||||
emit!(self, PseudoInstruction::LoadZeroSuperMethod { idx });
|
||||
}
|
||||
}
|
||||
self.compile_call_helper(0, args)?;
|
||||
@@ -6359,7 +6376,7 @@ impl Compiler {
|
||||
// LOAD_ATTR_METHOD pushes [method, self_or_null] on stack
|
||||
self.compile_expression(value)?;
|
||||
let idx = self.name(attr.as_str());
|
||||
emit!(self, Instruction::LoadAttrMethod { idx });
|
||||
emit!(self, PseudoInstruction::LoadAttrMethod { idx });
|
||||
self.compile_call_helper(0, args)?;
|
||||
}
|
||||
} else {
|
||||
@@ -6505,7 +6522,7 @@ impl Compiler {
|
||||
fn compile_comprehension(
|
||||
&mut self,
|
||||
name: &str,
|
||||
init_collection: Option<Instruction>,
|
||||
init_collection: Option<AnyInstruction>,
|
||||
generators: &[Comprehension],
|
||||
compile_element: &dyn Fn(&mut Self) -> CompileResult<()>,
|
||||
comprehension_type: ComprehensionType,
|
||||
@@ -6651,7 +6668,7 @@ impl Compiler {
|
||||
compile_element(self)?;
|
||||
|
||||
for (loop_block, after_block, is_async) in loop_labels.iter().rev().copied() {
|
||||
emit!(self, Instruction::Jump { target: loop_block });
|
||||
emit!(self, PseudoInstruction::Jump { target: loop_block });
|
||||
|
||||
self.switch_to_block(after_block);
|
||||
if is_async {
|
||||
@@ -6728,7 +6745,7 @@ impl Compiler {
|
||||
/// This generates bytecode inline without creating a new code object
|
||||
fn compile_inlined_comprehension(
|
||||
&mut self,
|
||||
init_collection: Option<Instruction>,
|
||||
init_collection: Option<AnyInstruction>,
|
||||
generators: &[Comprehension],
|
||||
compile_element: &dyn Fn(&mut Self) -> CompileResult<()>,
|
||||
_has_an_async_gen: bool,
|
||||
@@ -6849,7 +6866,7 @@ impl Compiler {
|
||||
|
||||
// Step 7: Close all loops
|
||||
for (loop_block, after_block, is_async) in loop_labels.iter().rev().copied() {
|
||||
emit!(self, Instruction::Jump { target: loop_block });
|
||||
emit!(self, PseudoInstruction::Jump { target: loop_block });
|
||||
self.switch_to_block(after_block);
|
||||
if is_async {
|
||||
emit!(self, Instruction::EndAsyncFor);
|
||||
@@ -6863,7 +6880,7 @@ impl Compiler {
|
||||
self.pop_fblock(FBlockType::TryExcept);
|
||||
|
||||
// Normal path: jump past cleanup
|
||||
emit!(self, Instruction::Jump { target: end_block });
|
||||
emit!(self, PseudoInstruction::Jump { target: end_block });
|
||||
|
||||
// Exception cleanup path
|
||||
self.switch_to_block(cleanup_block);
|
||||
@@ -6936,14 +6953,14 @@ impl Compiler {
|
||||
}
|
||||
|
||||
// Low level helper functions:
|
||||
fn _emit(&mut self, instr: Instruction, arg: OpArg, target: BlockIdx) {
|
||||
fn _emit<I: Into<AnyInstruction>>(&mut self, instr: I, arg: OpArg, target: BlockIdx) {
|
||||
let range = self.current_source_range;
|
||||
let source = self.source_file.to_source_code();
|
||||
let location = source.source_location(range.start(), PositionEncoding::Utf8);
|
||||
let end_location = source.source_location(range.end(), PositionEncoding::Utf8);
|
||||
let except_handler = self.current_except_handler();
|
||||
self.current_block().instructions.push(ir::InstructionInfo {
|
||||
instr,
|
||||
instr: instr.into(),
|
||||
arg,
|
||||
target,
|
||||
location,
|
||||
@@ -6952,14 +6969,14 @@ impl Compiler {
|
||||
});
|
||||
}
|
||||
|
||||
fn emit_no_arg(&mut self, ins: Instruction) {
|
||||
fn emit_no_arg<I: Into<AnyInstruction>>(&mut self, ins: I) {
|
||||
self._emit(ins, OpArg::null(), BlockIdx::NULL)
|
||||
}
|
||||
|
||||
fn emit_arg<A: OpArgType, T: EmitArg<A>>(
|
||||
fn emit_arg<A: OpArgType, T: EmitArg<A>, I: Into<AnyInstruction>>(
|
||||
&mut self,
|
||||
arg: T,
|
||||
f: impl FnOnce(OpArgMarker<A>) -> Instruction,
|
||||
f: impl FnOnce(OpArgMarker<A>) -> I,
|
||||
) {
|
||||
let (op, arg, target) = arg.emit(f);
|
||||
self._emit(op, arg, target)
|
||||
@@ -6984,9 +7001,9 @@ impl Compiler {
|
||||
|
||||
fn emit_return_value(&mut self) {
|
||||
if let Some(inst) = self.current_block().instructions.last_mut()
|
||||
&& let Instruction::LoadConst { idx } = inst.instr
|
||||
&& let AnyInstruction::Real(Instruction::LoadConst { idx }) = inst.instr
|
||||
{
|
||||
inst.instr = Instruction::ReturnConst { idx };
|
||||
inst.instr = Instruction::ReturnConst { idx }.into();
|
||||
return;
|
||||
}
|
||||
emit!(self, Instruction::ReturnValue)
|
||||
@@ -7435,23 +7452,28 @@ impl Compiler {
|
||||
}
|
||||
|
||||
trait EmitArg<Arg: OpArgType> {
|
||||
fn emit(
|
||||
fn emit<I: Into<AnyInstruction>>(
|
||||
self,
|
||||
f: impl FnOnce(OpArgMarker<Arg>) -> Instruction,
|
||||
) -> (Instruction, OpArg, BlockIdx);
|
||||
f: impl FnOnce(OpArgMarker<Arg>) -> I,
|
||||
) -> (AnyInstruction, OpArg, BlockIdx);
|
||||
}
|
||||
|
||||
impl<T: OpArgType> EmitArg<T> for T {
|
||||
fn emit(self, f: impl FnOnce(OpArgMarker<T>) -> Instruction) -> (Instruction, OpArg, BlockIdx) {
|
||||
fn emit<I: Into<AnyInstruction>>(
|
||||
self,
|
||||
f: impl FnOnce(OpArgMarker<T>) -> I,
|
||||
) -> (AnyInstruction, OpArg, BlockIdx) {
|
||||
let (marker, arg) = OpArgMarker::new(self);
|
||||
(f(marker), arg, BlockIdx::NULL)
|
||||
(f(marker).into(), arg, BlockIdx::NULL)
|
||||
}
|
||||
}
|
||||
|
||||
impl EmitArg<bytecode::Label> for BlockIdx {
|
||||
fn emit(
|
||||
fn emit<I: Into<AnyInstruction>>(
|
||||
self,
|
||||
f: impl FnOnce(OpArgMarker<bytecode::Label>) -> Instruction,
|
||||
) -> (Instruction, OpArg, BlockIdx) {
|
||||
(f(OpArgMarker::marker()), OpArg::null(), self)
|
||||
f: impl FnOnce(OpArgMarker<bytecode::Label>) -> I,
|
||||
) -> (AnyInstruction, OpArg, BlockIdx) {
|
||||
(f(OpArgMarker::marker()).into(), OpArg::null(), self)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+58
-29
@@ -1,12 +1,14 @@
|
||||
use core::ops;
|
||||
|
||||
use crate::{IndexMap, IndexSet, error::InternalError};
|
||||
|
||||
use rustpython_compiler_core::{
|
||||
OneIndexed, SourceLocation,
|
||||
bytecode::{
|
||||
Arg, CodeFlags, CodeObject, CodeUnit, CodeUnits, ConstantData, ExceptionTableEntry,
|
||||
InstrDisplayContext, Instruction, Label, OpArg, PyCodeLocationInfoKind,
|
||||
encode_exception_table, encode_load_attr_arg, encode_load_super_attr_arg,
|
||||
AnyInstruction, Arg, CodeFlags, CodeObject, CodeUnit, CodeUnits, ConstantData,
|
||||
ExceptionTableEntry, InstrDisplayContext, Instruction, InstructionMetadata, Label, OpArg,
|
||||
PseudoInstruction, PyCodeLocationInfoKind, encode_exception_table, encode_load_attr_arg,
|
||||
encode_load_super_attr_arg,
|
||||
},
|
||||
varint::{write_signed_varint, write_varint},
|
||||
};
|
||||
@@ -85,7 +87,7 @@ impl ops::IndexMut<BlockIdx> for Vec<Block> {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InstructionInfo {
|
||||
pub instr: Instruction,
|
||||
pub instr: AnyInstruction,
|
||||
pub arg: OpArg,
|
||||
pub target: BlockIdx,
|
||||
pub location: SourceLocation,
|
||||
@@ -195,48 +197,73 @@ impl CodeInfo {
|
||||
.filter(|b| b.next != BlockIdx::NULL || !b.instructions.is_empty())
|
||||
{
|
||||
for info in &mut block.instructions {
|
||||
match info.instr {
|
||||
// Special case for:
|
||||
// - `Instruction::LoadAttr`
|
||||
// - `Instruction::LoadSuperAttr`
|
||||
|
||||
if let Some(instr) = info.instr.real() {
|
||||
match instr {
|
||||
// LOAD_ATTR → encode with method flag=0
|
||||
Instruction::LoadAttr { idx } => {
|
||||
let encoded = encode_load_attr_arg(idx.get(info.arg), false);
|
||||
info.arg = OpArg(encoded);
|
||||
info.instr = Instruction::LoadAttr { idx: Arg::marker() }.into();
|
||||
}
|
||||
// LOAD_SUPER_ATTR → encode with flags=0b10 (method=0, class=1)
|
||||
Instruction::LoadSuperAttr { arg: idx } => {
|
||||
let encoded =
|
||||
encode_load_super_attr_arg(idx.get(info.arg), false, true);
|
||||
info.arg = OpArg(encoded);
|
||||
info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() }.into();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
let instr = info.instr.expect_pseudo();
|
||||
|
||||
match instr {
|
||||
// LOAD_ATTR_METHOD pseudo → LOAD_ATTR (with method flag=1)
|
||||
Instruction::LoadAttrMethod { idx } => {
|
||||
PseudoInstruction::LoadAttrMethod { idx } => {
|
||||
let encoded = encode_load_attr_arg(idx.get(info.arg), true);
|
||||
info.arg = OpArg(encoded);
|
||||
info.instr = Instruction::LoadAttr { idx: Arg::marker() };
|
||||
}
|
||||
// LOAD_ATTR → encode with method flag=0
|
||||
Instruction::LoadAttr { idx } => {
|
||||
let encoded = encode_load_attr_arg(idx.get(info.arg), false);
|
||||
info.arg = OpArg(encoded);
|
||||
info.instr = Instruction::LoadAttr { idx: Arg::marker() };
|
||||
info.instr = Instruction::LoadAttr { idx: Arg::marker() }.into();
|
||||
}
|
||||
// POP_BLOCK pseudo → NOP
|
||||
Instruction::PopBlock => {
|
||||
info.instr = Instruction::Nop;
|
||||
PseudoInstruction::PopBlock => {
|
||||
info.instr = Instruction::Nop.into();
|
||||
}
|
||||
// LOAD_SUPER_METHOD pseudo → LOAD_SUPER_ATTR (flags=0b11: method=1, class=1)
|
||||
Instruction::LoadSuperMethod { idx } => {
|
||||
PseudoInstruction::LoadSuperMethod { idx } => {
|
||||
let encoded = encode_load_super_attr_arg(idx.get(info.arg), true, true);
|
||||
info.arg = OpArg(encoded);
|
||||
info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() };
|
||||
info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() }.into();
|
||||
}
|
||||
// LOAD_ZERO_SUPER_ATTR pseudo → LOAD_SUPER_ATTR (flags=0b00: method=0, class=0)
|
||||
Instruction::LoadZeroSuperAttr { idx } => {
|
||||
PseudoInstruction::LoadZeroSuperAttr { idx } => {
|
||||
let encoded = encode_load_super_attr_arg(idx.get(info.arg), false, false);
|
||||
info.arg = OpArg(encoded);
|
||||
info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() };
|
||||
info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() }.into();
|
||||
}
|
||||
// LOAD_ZERO_SUPER_METHOD pseudo → LOAD_SUPER_ATTR (flags=0b01: method=1, class=0)
|
||||
Instruction::LoadZeroSuperMethod { idx } => {
|
||||
PseudoInstruction::LoadZeroSuperMethod { idx } => {
|
||||
let encoded = encode_load_super_attr_arg(idx.get(info.arg), true, false);
|
||||
info.arg = OpArg(encoded);
|
||||
info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() };
|
||||
info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() }.into();
|
||||
}
|
||||
// LOAD_SUPER_ATTR → encode with flags=0b10 (method=0, class=1)
|
||||
Instruction::LoadSuperAttr { arg: idx } => {
|
||||
let encoded = encode_load_super_attr_arg(idx.get(info.arg), false, true);
|
||||
info.arg = OpArg(encoded);
|
||||
info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() };
|
||||
PseudoInstruction::Jump { .. } => {
|
||||
// PseudoInstruction::Jump instructions are handled later
|
||||
}
|
||||
PseudoInstruction::JumpNoInterrupt { .. }
|
||||
| PseudoInstruction::Reserved258
|
||||
| PseudoInstruction::SetupCleanup
|
||||
| PseudoInstruction::SetupFinally
|
||||
| PseudoInstruction::SetupWith
|
||||
| PseudoInstruction::StoreFastMaybeNull => {
|
||||
unimplemented!("Got a placeholder pseudo instruction ({instr:?})")
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -277,7 +304,9 @@ impl CodeInfo {
|
||||
|
||||
// Convert JUMP pseudo to real instructions (direction depends on offset)
|
||||
let op = match info.instr {
|
||||
Instruction::Jump { .. } if target != BlockIdx::NULL => {
|
||||
AnyInstruction::Pseudo(PseudoInstruction::Jump { .. })
|
||||
if target != BlockIdx::NULL =>
|
||||
{
|
||||
let target_offset = block_to_offset[target.idx()].0;
|
||||
if target_offset > current_offset {
|
||||
Instruction::JumpForward {
|
||||
@@ -289,7 +318,7 @@ impl CodeInfo {
|
||||
}
|
||||
}
|
||||
}
|
||||
other => other,
|
||||
other => other.expect_real(),
|
||||
};
|
||||
|
||||
let (extras, lo_arg) = info.arg.split();
|
||||
|
||||
@@ -15,7 +15,11 @@ use num_complex::Complex64;
|
||||
use rustpython_wtf8::{Wtf8, Wtf8Buf};
|
||||
|
||||
pub use crate::bytecode::{
|
||||
instruction::{Arg, Instruction, decode_load_super_attr_arg, encode_load_super_attr_arg},
|
||||
instruction::{
|
||||
AnyInstruction, Arg, Instruction, InstructionMetadata, PseudoInstruction,
|
||||
decode_load_attr_arg, decode_load_super_attr_arg, encode_load_attr_arg,
|
||||
encode_load_super_attr_arg,
|
||||
},
|
||||
oparg::{
|
||||
BinaryOperator, BuildSliceArgCount, ComparisonOperator, ConvertValueOparg,
|
||||
IntrinsicFunction1, IntrinsicFunction2, Invert, Label, MakeFunctionFlags, NameIdx, OpArg,
|
||||
@@ -96,20 +100,6 @@ pub fn find_exception_handler(table: &[u8], offset: u32) -> Option<ExceptionTabl
|
||||
None
|
||||
}
|
||||
|
||||
/// Encode LOAD_ATTR oparg: bit 0 = method flag, bits 1+ = name index.
|
||||
#[inline]
|
||||
pub const fn encode_load_attr_arg(name_idx: u32, is_method: bool) -> u32 {
|
||||
(name_idx << 1) | (is_method as u32)
|
||||
}
|
||||
|
||||
/// Decode LOAD_ATTR oparg: returns (name_idx, is_method).
|
||||
#[inline]
|
||||
pub const fn decode_load_attr_arg(oparg: u32) -> (u32, bool) {
|
||||
let is_method = (oparg & 1) == 1;
|
||||
let name_idx = oparg >> 1;
|
||||
(name_idx, is_method)
|
||||
}
|
||||
|
||||
/// CPython 3.11+ linetable location info codes
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
|
||||
@@ -2,7 +2,7 @@ use core::{fmt, marker::PhantomData, mem};
|
||||
|
||||
use crate::{
|
||||
bytecode::{
|
||||
BorrowedConstant, Constant, InstrDisplayContext, decode_load_attr_arg,
|
||||
BorrowedConstant, Constant, InstrDisplayContext,
|
||||
oparg::{
|
||||
BinaryOperator, BuildSliceArgCount, ComparisonOperator, ConvertValueOparg,
|
||||
IntrinsicFunction1, IntrinsicFunction2, Invert, Label, MakeFunctionFlags, NameIdx,
|
||||
@@ -12,10 +12,13 @@ use crate::{
|
||||
marshal::MarshalError,
|
||||
};
|
||||
|
||||
/// A Single bytecode instruction.
|
||||
/// Instructions are ordered to match CPython 3.13 opcode numbers exactly.
|
||||
/// HAVE_ARGUMENT = 44: opcodes 0-43 have no argument, 44+ have arguments.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
/// A Single bytecode instruction that are executed by the VM.
|
||||
///
|
||||
/// Currently aligned with CPython 3.13.
|
||||
///
|
||||
/// ## See also
|
||||
/// - [CPython opcode IDs](https://github.com/python/cpython/blob/627894459a84be3488a1789919679c997056a03c/Include/opcode_ids.h)
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum Instruction {
|
||||
// ==================== No-argument instructions (opcode < 44) ====================
|
||||
@@ -245,20 +248,6 @@ pub enum Instruction {
|
||||
Resume {
|
||||
arg: Arg<u32>,
|
||||
} = 149,
|
||||
// ===== LOAD_SUPER_* Pseudo Opcodes (136-138) =====
|
||||
// These are converted to LoadSuperAttr during bytecode finalization.
|
||||
// "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>,
|
||||
} = 136, // CPython uses pseudo-op 260
|
||||
LoadZeroSuperAttr {
|
||||
idx: Arg<NameIdx>,
|
||||
} = 137, // CPython uses pseudo-op 261
|
||||
LoadZeroSuperMethod {
|
||||
idx: Arg<NameIdx>,
|
||||
} = 138, // CPython uses pseudo-op 262
|
||||
// ==================== RustPython-only instructions (119-135) ====================
|
||||
// Ideally, we want to be fully aligned with CPython opcodes, but we still have some leftovers.
|
||||
// So we assign random IDs to these opcodes.
|
||||
@@ -290,15 +279,8 @@ pub enum Instruction {
|
||||
JumpIfNotExcMatch(Arg<Label>) = 131,
|
||||
SetExcInfo = 134,
|
||||
Subscript = 135,
|
||||
// ===== Pseudo Opcodes (252+) ======
|
||||
Jump {
|
||||
target: Arg<Label>,
|
||||
} = 252, // CPython uses pseudo-op 256
|
||||
LoadClosure(Arg<NameIdx>) = 253, // CPython uses pseudo-op 258
|
||||
LoadAttrMethod {
|
||||
idx: Arg<NameIdx>,
|
||||
} = 254, // CPython uses pseudo-op 259
|
||||
PopBlock = 255, // CPython uses pseudo-op 263
|
||||
// Pseudos (needs to be moved to `PseudoInstruction` enum.
|
||||
LoadClosure(Arg<NameIdx>) = 253, // TODO: Move to pseudos
|
||||
}
|
||||
|
||||
const _: () = assert!(mem::size_of::<Instruction>() == 1);
|
||||
@@ -307,7 +289,7 @@ impl From<Instruction> for u8 {
|
||||
#[inline]
|
||||
fn from(ins: Instruction) -> Self {
|
||||
// SAFETY: there's no padding bits
|
||||
unsafe { core::mem::transmute::<Instruction, Self>(ins) }
|
||||
unsafe { mem::transmute::<Instruction, Self>(ins) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,6 +305,9 @@ impl TryFrom<u8> for Instruction {
|
||||
// Resume has a non-contiguous opcode (149)
|
||||
let resume_id = u8::from(Self::Resume { arg: Arg::marker() });
|
||||
|
||||
// TODO: Remove this; This instruction needs to be pseudo
|
||||
let load_closure = u8::from(Self::LoadClosure(Arg::marker()));
|
||||
|
||||
// RustPython-only opcodes (explicit list to avoid gaps like 125-127)
|
||||
let custom_ops: &[u8] = &[
|
||||
u8::from(Self::Break {
|
||||
@@ -354,37 +339,25 @@ impl TryFrom<u8> for Instruction {
|
||||
u8::from(Self::JumpIfNotExcMatch(Arg::marker())),
|
||||
u8::from(Self::SetExcInfo),
|
||||
u8::from(Self::Subscript),
|
||||
// LOAD_SUPER_* pseudo opcodes (136-138)
|
||||
u8::from(Self::LoadSuperMethod { idx: Arg::marker() }),
|
||||
u8::from(Self::LoadZeroSuperAttr { idx: Arg::marker() }),
|
||||
u8::from(Self::LoadZeroSuperMethod { idx: Arg::marker() }),
|
||||
];
|
||||
|
||||
// Pseudo opcodes (252-255)
|
||||
let pseudo_start = u8::from(Self::Jump {
|
||||
target: Arg::marker(),
|
||||
});
|
||||
let pseudo_end = u8::from(Self::PopBlock);
|
||||
|
||||
if (cpython_start..=cpython_end).contains(&value)
|
||||
|| value == resume_id
|
||||
|| value == load_closure
|
||||
|| custom_ops.contains(&value)
|
||||
|| (pseudo_start..=pseudo_end).contains(&value)
|
||||
{
|
||||
Ok(unsafe { core::mem::transmute::<u8, Self>(value) })
|
||||
Ok(unsafe { mem::transmute::<u8, Self>(value) })
|
||||
} else {
|
||||
Err(Self::Error::InvalidBytecode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Instruction {
|
||||
/// Gets the label stored inside this instruction, if it exists
|
||||
impl InstructionMetadata for Instruction {
|
||||
#[inline]
|
||||
pub const fn label_arg(&self) -> Option<Arg<Label>> {
|
||||
fn label_arg(&self) -> Option<Arg<Label>> {
|
||||
match self {
|
||||
Self::Jump { target: l }
|
||||
| Self::JumpBackward { target: l }
|
||||
Self::JumpBackward { target: l }
|
||||
| Self::JumpBackwardNoInterrupt { target: l }
|
||||
| Self::JumpForward { target: l }
|
||||
| Self::JumpIfNotExcMatch(l)
|
||||
@@ -400,20 +373,10 @@ impl Instruction {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this is an unconditional branching
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rustpython_compiler_core::bytecode::{Arg, Instruction};
|
||||
/// let jump_inst = Instruction::Jump { target: Arg::marker() };
|
||||
/// assert!(jump_inst.unconditional_branch())
|
||||
/// ```
|
||||
pub const fn unconditional_branch(&self) -> bool {
|
||||
fn unconditional_branch(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::Jump { .. }
|
||||
| Self::JumpForward { .. }
|
||||
Self::JumpForward { .. }
|
||||
| Self::JumpBackward { .. }
|
||||
| Self::JumpBackwardNoInterrupt { .. }
|
||||
| Self::Continue { .. }
|
||||
@@ -425,18 +388,7 @@ impl Instruction {
|
||||
)
|
||||
}
|
||||
|
||||
/// What effect this instruction has on the stack
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rustpython_compiler_core::bytecode::{Arg, Instruction, Label};
|
||||
/// let (target, jump_arg) = Arg::new(Label(0xF));
|
||||
/// let jump_instruction = Instruction::Jump { target };
|
||||
/// assert_eq!(jump_instruction.stack_effect(jump_arg, true), 0);
|
||||
/// ```
|
||||
///
|
||||
pub fn stack_effect(&self, arg: OpArg, jump: bool) -> i32 {
|
||||
fn stack_effect(&self, arg: OpArg, jump: bool) -> i32 {
|
||||
match self {
|
||||
Self::Nop => 0,
|
||||
Self::ImportName { .. } => -1,
|
||||
@@ -456,13 +408,10 @@ impl Instruction {
|
||||
Self::DeleteGlobal(_) => 0,
|
||||
Self::DeleteDeref(_) => 0,
|
||||
Self::LoadFromDictOrDeref(_) => 1,
|
||||
Self::LoadClosure(_) => 1,
|
||||
Self::Subscript => -1,
|
||||
Self::StoreSubscr => -3,
|
||||
Self::DeleteSubscr => -2,
|
||||
Self::LoadAttr { .. } => 0,
|
||||
// LoadAttrMethod: pop obj, push method + self_or_null
|
||||
Self::LoadAttrMethod { .. } => 1,
|
||||
Self::StoreAttr { .. } => -2,
|
||||
Self::DeleteAttr { .. } => -1,
|
||||
Self::LoadConst { .. } => 1,
|
||||
@@ -480,7 +429,6 @@ impl Instruction {
|
||||
Self::CallIntrinsic2 { .. } => -1, // Takes 2, pushes 1
|
||||
Self::Continue { .. } => 0,
|
||||
Self::Break { .. } => 0,
|
||||
Self::Jump { .. } => 0,
|
||||
Self::PopJumpIfTrue { .. } => -1,
|
||||
Self::PopJumpIfFalse { .. } => -1,
|
||||
Self::JumpIfTrueOrPop { .. } => {
|
||||
@@ -542,7 +490,6 @@ impl Instruction {
|
||||
Self::SetupAnnotations => 0,
|
||||
Self::BeforeWith => 1, // push __exit__, then replace ctx_mgr with __enter__ result
|
||||
Self::WithExceptStart => 1, // push __exit__ result
|
||||
Self::PopBlock => 0,
|
||||
Self::RaiseVarargs { kind } => {
|
||||
// Stack effects for different raise kinds:
|
||||
// - Reraise (0): gets from VM state, no stack pop
|
||||
@@ -615,9 +562,6 @@ impl Instruction {
|
||||
if load_method { -3 + 2 } else { -3 + 1 }
|
||||
}
|
||||
// Pseudo instructions (calculated before conversion)
|
||||
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::Cache => 0,
|
||||
Self::BinarySlice => 0,
|
||||
Self::BinaryOpInplaceAddUnicode => 0,
|
||||
@@ -644,25 +588,12 @@ impl Instruction {
|
||||
Self::StoreFastStoreFast { .. } => 0,
|
||||
Self::PopJumpIfNone { .. } => 0,
|
||||
Self::PopJumpIfNotNone { .. } => 0,
|
||||
Self::LoadClosure(_) => 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display<'a>(
|
||||
&'a self,
|
||||
arg: OpArg,
|
||||
ctx: &'a impl InstrDisplayContext,
|
||||
) -> impl fmt::Display + 'a {
|
||||
struct FmtFn<F>(F);
|
||||
impl<F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result> fmt::Display for FmtFn<F> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
(self.0)(f)
|
||||
}
|
||||
}
|
||||
FmtFn(move |f: &mut fmt::Formatter<'_>| self.fmt_dis(arg, f, ctx, false, 0, 0))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn fmt_dis(
|
||||
fn fmt_dis(
|
||||
&self,
|
||||
arg: OpArg,
|
||||
f: &mut fmt::Formatter<'_>,
|
||||
@@ -765,7 +696,6 @@ impl Instruction {
|
||||
Self::ImportFrom { idx } => w!(IMPORT_FROM, name = idx),
|
||||
Self::ImportName { idx } => w!(IMPORT_NAME, name = idx),
|
||||
Self::IsOp(inv) => w!(IS_OP, ?inv),
|
||||
Self::Jump { target } => w!(JUMP, target),
|
||||
Self::JumpBackward { target } => w!(JUMP_BACKWARD, target),
|
||||
Self::JumpBackwardNoInterrupt { target } => w!(JUMP_BACKWARD_NO_INTERRUPT, target),
|
||||
Self::JumpForward { target } => w!(JUMP_FORWARD, target),
|
||||
@@ -787,7 +717,6 @@ impl Instruction {
|
||||
write!(f, "{:pad$}({}, {})", "LOAD_ATTR", encoded, attr_name)
|
||||
}
|
||||
}
|
||||
Self::LoadAttrMethod { idx } => w!(LOAD_ATTR_METHOD, name = idx),
|
||||
Self::LoadBuildClass => w!(LOAD_BUILD_CLASS),
|
||||
Self::LoadFromDictOrDeref(i) => w!(LOAD_FROM_DICT_OR_DEREF, cell_name = i),
|
||||
Self::LoadClosure(i) => w!(LOAD_CLOSURE, cell_name = i),
|
||||
@@ -807,9 +736,6 @@ impl Instruction {
|
||||
"LOAD_SUPER_ATTR", encoded, attr_name, load_method, has_class
|
||||
)
|
||||
}
|
||||
Self::LoadSuperMethod { idx } => w!(LOAD_SUPER_METHOD, name = idx),
|
||||
Self::LoadZeroSuperAttr { idx } => w!(LOAD_ZERO_SUPER_ATTR, name = idx),
|
||||
Self::LoadZeroSuperMethod { idx } => w!(LOAD_ZERO_SUPER_METHOD, name = idx),
|
||||
Self::MakeFunction => w!(MAKE_FUNCTION),
|
||||
Self::MapAdd { i } => w!(MAP_ADD, i),
|
||||
Self::MatchClass(arg) => w!(MATCH_CLASS, arg),
|
||||
@@ -817,7 +743,6 @@ impl Instruction {
|
||||
Self::MatchMapping => w!(MATCH_MAPPING),
|
||||
Self::MatchSequence => w!(MATCH_SEQUENCE),
|
||||
Self::Nop => w!(NOP),
|
||||
Self::PopBlock => w!(POP_BLOCK),
|
||||
Self::PopExcept => w!(POP_EXCEPT),
|
||||
Self::PopJumpIfFalse { target } => w!(POP_JUMP_IF_FALSE, target),
|
||||
Self::PopJumpIfTrue { target } => w!(POP_JUMP_IF_TRUE, target),
|
||||
@@ -863,6 +788,266 @@ impl Instruction {
|
||||
}
|
||||
}
|
||||
|
||||
/// Instructions used by the compiler. They are not executed by the VM.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(u16)]
|
||||
pub enum PseudoInstruction {
|
||||
Jump {
|
||||
target: Arg<Label>,
|
||||
} = 256,
|
||||
JumpNoInterrupt {
|
||||
target: Arg<Label>,
|
||||
} = 257, // Placeholder
|
||||
Reserved258 = 258,
|
||||
LoadAttrMethod {
|
||||
idx: Arg<NameIdx>,
|
||||
} = 259,
|
||||
// "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,
|
||||
LoadZeroSuperAttr {
|
||||
idx: Arg<NameIdx>,
|
||||
} = 261,
|
||||
LoadZeroSuperMethod {
|
||||
idx: Arg<NameIdx>,
|
||||
} = 262,
|
||||
PopBlock = 263,
|
||||
SetupCleanup = 264, // Placeholder
|
||||
SetupFinally = 265, // Placeholder
|
||||
SetupWith = 266, // Placeholder
|
||||
StoreFastMaybeNull = 267, // Placeholder
|
||||
}
|
||||
|
||||
const _: () = assert!(mem::size_of::<PseudoInstruction>() == 2);
|
||||
|
||||
impl From<PseudoInstruction> for u16 {
|
||||
#[inline]
|
||||
fn from(ins: PseudoInstruction) -> Self {
|
||||
// SAFETY: there's no padding bits
|
||||
unsafe { mem::transmute::<PseudoInstruction, Self>(ins) }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u16> for PseudoInstruction {
|
||||
type Error = MarshalError;
|
||||
|
||||
#[inline]
|
||||
fn try_from(value: u16) -> Result<Self, MarshalError> {
|
||||
let start = u16::from(Self::Jump {
|
||||
target: Arg::marker(),
|
||||
});
|
||||
let end = u16::from(Self::StoreFastMaybeNull);
|
||||
|
||||
if (start..=end).contains(&value) {
|
||||
Ok(unsafe { mem::transmute::<u16, Self>(value) })
|
||||
} else {
|
||||
Err(Self::Error::InvalidBytecode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InstructionMetadata for PseudoInstruction {
|
||||
fn label_arg(&self) -> Option<Arg<Label>> {
|
||||
match self {
|
||||
Self::Jump { target: l } => Some(*l),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn unconditional_branch(&self) -> bool {
|
||||
matches!(self, Self::Jump { .. },)
|
||||
}
|
||||
|
||||
fn stack_effect(&self, _arg: OpArg, _jump: bool) -> i32 {
|
||||
match self {
|
||||
Self::Jump { .. } => 0,
|
||||
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::PopBlock => 0,
|
||||
Self::SetupCleanup => 0,
|
||||
Self::SetupFinally => 0,
|
||||
Self::SetupWith => 0,
|
||||
Self::StoreFastMaybeNull => 0,
|
||||
Self::Reserved258 => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_dis(
|
||||
&self,
|
||||
_arg: OpArg,
|
||||
_f: &mut fmt::Formatter<'_>,
|
||||
_ctx: &impl InstrDisplayContext,
|
||||
_expand_code_objects: bool,
|
||||
_pad: usize,
|
||||
_level: usize,
|
||||
) -> fmt::Result {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum AnyInstruction {
|
||||
Real(Instruction),
|
||||
Pseudo(PseudoInstruction),
|
||||
}
|
||||
|
||||
impl From<Instruction> for AnyInstruction {
|
||||
fn from(value: Instruction) -> Self {
|
||||
Self::Real(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PseudoInstruction> for AnyInstruction {
|
||||
fn from(value: PseudoInstruction) -> Self {
|
||||
Self::Pseudo(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for AnyInstruction {
|
||||
type Error = MarshalError;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
Ok(Instruction::try_from(value)?.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u16> for AnyInstruction {
|
||||
type Error = MarshalError;
|
||||
|
||||
fn try_from(value: u16) -> Result<Self, Self::Error> {
|
||||
match u8::try_from(value) {
|
||||
Ok(v) => v.try_into(),
|
||||
Err(_) => Ok(PseudoInstruction::try_from(value)?.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! inst_either {
|
||||
(fn $name:ident ( &self $(, $arg:ident : $arg_ty:ty )* ) -> $ret:ty ) => {
|
||||
fn $name(&self $(, $arg : $arg_ty )* ) -> $ret {
|
||||
match self {
|
||||
Self::Real(op) => op.$name($($arg),*),
|
||||
Self::Pseudo(op) => op.$name($($arg),*),
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl InstructionMetadata for AnyInstruction {
|
||||
inst_either!(fn label_arg(&self) -> Option<Arg<Label>>);
|
||||
|
||||
inst_either!(fn unconditional_branch(&self) -> bool);
|
||||
|
||||
inst_either!(fn stack_effect(&self, arg: OpArg, jump: bool) -> i32);
|
||||
|
||||
inst_either!(fn fmt_dis(
|
||||
&self,
|
||||
arg: OpArg,
|
||||
f: &mut fmt::Formatter<'_>,
|
||||
ctx: &impl InstrDisplayContext,
|
||||
expand_code_objects: bool,
|
||||
pad: usize,
|
||||
level: usize
|
||||
) -> fmt::Result);
|
||||
}
|
||||
|
||||
impl AnyInstruction {
|
||||
/// Gets the inner value of [`Self::Real`].
|
||||
pub const fn real(self) -> Option<Instruction> {
|
||||
match self {
|
||||
Self::Real(ins) => Some(ins),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the inner value of [`Self::Pseudo`].
|
||||
pub const fn pseudo(self) -> Option<PseudoInstruction> {
|
||||
match self {
|
||||
Self::Pseudo(ins) => Some(ins),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as [`Self::real`] but panics if wasn't called on [`Self::Real`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If was called on something else other than [`Self::Real`].
|
||||
pub const fn expect_real(self) -> Instruction {
|
||||
self.real()
|
||||
.expect("Expected Instruction::Real, found Instruction::Pseudo")
|
||||
}
|
||||
|
||||
/// Same as [`Self::pseudo`] but panics if wasn't called on [`Self::Pseudo`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If was called on something else other than [`Self::Pseudo`].
|
||||
pub const fn expect_pseudo(self) -> PseudoInstruction {
|
||||
self.pseudo()
|
||||
.expect("Expected Instruction::Pseudo, found Instruction::Real")
|
||||
}
|
||||
}
|
||||
|
||||
pub trait InstructionMetadata {
|
||||
/// Gets the label stored inside this instruction, if it exists.
|
||||
fn label_arg(&self) -> Option<Arg<Label>>;
|
||||
|
||||
/// Whether this is an unconditional branching.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rustpython_compiler_core::bytecode::{Arg, Instruction, InstructionMetadata};
|
||||
/// let jump_inst = Instruction::JumpForward { target: Arg::marker() };
|
||||
/// assert!(jump_inst.unconditional_branch())
|
||||
/// ```
|
||||
fn unconditional_branch(&self) -> bool;
|
||||
|
||||
/// What effect this instruction has on the stack
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// 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);
|
||||
/// ```
|
||||
fn stack_effect(&self, arg: OpArg, jump: bool) -> i32;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn fmt_dis(
|
||||
&self,
|
||||
arg: OpArg,
|
||||
f: &mut fmt::Formatter<'_>,
|
||||
ctx: &impl InstrDisplayContext,
|
||||
expand_code_objects: bool,
|
||||
pad: usize,
|
||||
level: usize,
|
||||
) -> fmt::Result;
|
||||
|
||||
fn display<'a>(
|
||||
&'a self,
|
||||
arg: OpArg,
|
||||
ctx: &'a impl InstrDisplayContext,
|
||||
) -> impl fmt::Display + 'a {
|
||||
struct FmtFn<F>(F);
|
||||
impl<F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result> fmt::Display for FmtFn<F> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
(self.0)(f)
|
||||
}
|
||||
}
|
||||
FmtFn(move |f: &mut fmt::Formatter<'_>| self.fmt_dis(arg, f, ctx, false, 0, 0))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Arg<T: OpArgType>(PhantomData<T>);
|
||||
|
||||
@@ -918,6 +1103,20 @@ impl<T: OpArgType> fmt::Debug for Arg<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode LOAD_ATTR oparg: bit 0 = method flag, bits 1+ = name index.
|
||||
#[inline]
|
||||
pub const fn encode_load_attr_arg(name_idx: u32, is_method: bool) -> u32 {
|
||||
(name_idx << 1) | (is_method as u32)
|
||||
}
|
||||
|
||||
/// Decode LOAD_ATTR oparg: returns (name_idx, is_method).
|
||||
#[inline]
|
||||
pub const fn decode_load_attr_arg(oparg: u32) -> (u32, bool) {
|
||||
let is_method = (oparg & 1) == 1;
|
||||
let name_idx = oparg >> 1;
|
||||
(name_idx, is_method)
|
||||
}
|
||||
|
||||
/// Encode LOAD_SUPER_ATTR oparg: bit 0 = load_method, bit 1 = has_class, bits 2+ = name index.
|
||||
#[inline]
|
||||
pub const fn encode_load_super_attr_arg(name_idx: u32, load_method: bool, has_class: bool) -> u32 {
|
||||
|
||||
@@ -80,7 +80,7 @@ impl OpArgState {
|
||||
#[inline(always)]
|
||||
pub fn get(&mut self, ins: CodeUnit) -> (Instruction, OpArg) {
|
||||
let arg = self.extend(ins.arg);
|
||||
if ins.op != Instruction::ExtendedArg {
|
||||
if !matches!(ins.op, Instruction::ExtendedArg) {
|
||||
self.reset();
|
||||
}
|
||||
(ins.op, arg)
|
||||
|
||||
@@ -212,7 +212,6 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> {
|
||||
match instruction {
|
||||
Instruction::ReturnValue
|
||||
| Instruction::ReturnConst { .. }
|
||||
| Instruction::Jump { .. }
|
||||
| Instruction::JumpBackward { .. }
|
||||
| Instruction::JumpBackwardNoInterrupt { .. }
|
||||
| Instruction::JumpForward { .. } => {
|
||||
@@ -561,8 +560,7 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> {
|
||||
}
|
||||
Instruction::ExtendedArg => Ok(()),
|
||||
|
||||
Instruction::Jump { target }
|
||||
| Instruction::JumpBackward { target }
|
||||
Instruction::JumpBackward { target }
|
||||
| Instruction::JumpBackwardNoInterrupt { target }
|
||||
| Instruction::JumpForward { target } => {
|
||||
let target_block = self.get_or_create_block(target.get(arg));
|
||||
@@ -596,10 +594,6 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> {
|
||||
}
|
||||
}
|
||||
Instruction::Nop => Ok(()),
|
||||
Instruction::PopBlock => {
|
||||
// TODO: block support
|
||||
Ok(())
|
||||
}
|
||||
Instruction::PopJumpIfFalse { target } => {
|
||||
let cond = self.stack.pop().ok_or(JitCompileError::BadBytecode)?;
|
||||
let val = self.boolean_val(cond)?;
|
||||
|
||||
+59
-41
@@ -5,29 +5,43 @@ mod opcode {
|
||||
use crate::vm::{
|
||||
AsObject, PyObjectRef, PyResult, VirtualMachine,
|
||||
builtins::{PyInt, PyIntRef},
|
||||
bytecode::Instruction,
|
||||
bytecode::{AnyInstruction, Instruction, InstructionMetadata, PseudoInstruction},
|
||||
};
|
||||
use core::ops::Deref;
|
||||
|
||||
struct Opcode(Instruction);
|
||||
#[derive(Clone, Copy)]
|
||||
struct Opcode(AnyInstruction);
|
||||
|
||||
impl Deref for Opcode {
|
||||
type Target = Instruction;
|
||||
type Target = AnyInstruction;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<i32> for Opcode {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: i32) -> Result<Self, Self::Error> {
|
||||
Ok(Self(
|
||||
u16::try_from(value)
|
||||
.map_err(|_| ())?
|
||||
.try_into()
|
||||
.map_err(|_| ())?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Opcode {
|
||||
// https://github.com/python/cpython/blob/bcee1c322115c581da27600f2ae55e5439c027eb/Include/opcode_ids.h#L238
|
||||
const HAVE_ARGUMENT: i32 = 44;
|
||||
|
||||
pub fn try_from_pyint(raw: PyIntRef, vm: &VirtualMachine) -> PyResult<Self> {
|
||||
let instruction = raw
|
||||
.try_to_primitive::<u8>(vm)
|
||||
.try_to_primitive::<u16>(vm)
|
||||
.and_then(|v| {
|
||||
Instruction::try_from(v).map_err(|_| {
|
||||
AnyInstruction::try_from(v).map_err(|_| {
|
||||
vm.new_exception_empty(vm.ctx.exceptions.value_error.to_owned())
|
||||
})
|
||||
})
|
||||
@@ -36,13 +50,14 @@ mod opcode {
|
||||
Ok(Self(instruction))
|
||||
}
|
||||
|
||||
/// Check if opcode is valid (can be converted to an Instruction)
|
||||
const fn inner(self) -> AnyInstruction {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Check if opcode is valid (can be converted to an AnyInstruction)
|
||||
#[must_use]
|
||||
pub fn is_valid(opcode: i32) -> bool {
|
||||
if !(0..=255).contains(&opcode) {
|
||||
return false;
|
||||
}
|
||||
Instruction::try_from(opcode as u8).is_ok()
|
||||
Self::try_from(opcode).is_ok()
|
||||
}
|
||||
|
||||
/// Check if instruction has an argument
|
||||
@@ -54,79 +69,82 @@ mod opcode {
|
||||
/// Check if instruction uses co_consts
|
||||
#[must_use]
|
||||
pub fn has_const(opcode: i32) -> bool {
|
||||
Self::is_valid(opcode)
|
||||
&& matches!(
|
||||
Instruction::try_from(opcode as u8),
|
||||
Ok(Instruction::LoadConst { .. } | Instruction::ReturnConst { .. })
|
||||
)
|
||||
matches!(
|
||||
Self::try_from(opcode).map(|op| op.inner()),
|
||||
Ok(AnyInstruction::Real(
|
||||
Instruction::LoadConst { .. } | Instruction::ReturnConst { .. }
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
/// Check if instruction uses co_names
|
||||
#[must_use]
|
||||
pub fn has_name(opcode: i32) -> bool {
|
||||
Self::is_valid(opcode)
|
||||
&& matches!(
|
||||
Instruction::try_from(opcode as u8),
|
||||
Ok(Instruction::DeleteAttr { .. }
|
||||
matches!(
|
||||
Self::try_from(opcode).map(|op| op.inner()),
|
||||
Ok(AnyInstruction::Real(
|
||||
Instruction::DeleteAttr { .. }
|
||||
| Instruction::DeleteGlobal(_)
|
||||
| Instruction::DeleteName(_)
|
||||
| Instruction::ImportFrom { .. }
|
||||
| Instruction::ImportName { .. }
|
||||
| Instruction::LoadAttr { .. }
|
||||
| Instruction::LoadGlobal(_)
|
||||
| Instruction::LoadAttrMethod { .. }
|
||||
| Instruction::LoadName(_)
|
||||
| Instruction::StoreAttr { .. }
|
||||
| Instruction::StoreGlobal(_)
|
||||
| Instruction::StoreName(_))
|
||||
)
|
||||
| Instruction::StoreName(_)
|
||||
) | AnyInstruction::Pseudo(PseudoInstruction::LoadAttrMethod { .. }))
|
||||
)
|
||||
}
|
||||
|
||||
/// Check if instruction is a jump
|
||||
#[must_use]
|
||||
pub fn has_jump(opcode: i32) -> bool {
|
||||
Self::is_valid(opcode)
|
||||
&& matches!(
|
||||
Instruction::try_from(opcode as u8),
|
||||
Ok(Instruction::Break { .. }
|
||||
matches!(
|
||||
Self::try_from(opcode).map(|op| op.inner()),
|
||||
Ok(AnyInstruction::Real(
|
||||
Instruction::Break { .. }
|
||||
| Instruction::Continue { .. }
|
||||
| Instruction::ForIter { .. }
|
||||
| Instruction::JumpIfFalseOrPop { .. }
|
||||
| Instruction::JumpIfNotExcMatch(_)
|
||||
| Instruction::JumpIfTrueOrPop { .. }
|
||||
| Instruction::Jump { .. }
|
||||
| Instruction::PopJumpIfFalse { .. }
|
||||
| Instruction::PopJumpIfTrue { .. }
|
||||
| Instruction::Send { .. })
|
||||
)
|
||||
| Instruction::Send { .. }
|
||||
) | AnyInstruction::Pseudo(PseudoInstruction::Jump { .. }))
|
||||
)
|
||||
}
|
||||
|
||||
/// Check if instruction uses co_freevars/co_cellvars
|
||||
#[must_use]
|
||||
pub fn has_free(opcode: i32) -> bool {
|
||||
Self::is_valid(opcode)
|
||||
&& matches!(
|
||||
Instruction::try_from(opcode as u8),
|
||||
Ok(Instruction::DeleteDeref(_)
|
||||
matches!(
|
||||
Self::try_from(opcode).map(|op| op.inner()),
|
||||
Ok(AnyInstruction::Real(
|
||||
Instruction::DeleteDeref(_)
|
||||
| Instruction::LoadFromDictOrDeref(_)
|
||||
| Instruction::LoadClosure(_)
|
||||
| Instruction::LoadDeref(_)
|
||||
| Instruction::StoreDeref(_))
|
||||
)
|
||||
| Instruction::StoreDeref(_)
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
/// Check if instruction uses co_varnames (local variables)
|
||||
#[must_use]
|
||||
pub fn has_local(opcode: i32) -> bool {
|
||||
Self::is_valid(opcode)
|
||||
&& matches!(
|
||||
Instruction::try_from(opcode as u8),
|
||||
Ok(Instruction::DeleteFast(_)
|
||||
matches!(
|
||||
Self::try_from(opcode).map(|op| op.inner()),
|
||||
Ok(AnyInstruction::Real(
|
||||
Instruction::DeleteFast(_)
|
||||
| Instruction::LoadFast(_)
|
||||
| Instruction::LoadFastAndClear(_)
|
||||
| Instruction::StoreFast(_)
|
||||
| Instruction::StoreFastLoadFast { .. })
|
||||
)
|
||||
| Instruction::StoreFastLoadFast { .. }
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
/// Check if instruction has exception info
|
||||
|
||||
+120
-144
@@ -7,7 +7,7 @@ use crate::{
|
||||
function::{PyCell, PyCellRef, PyFunction},
|
||||
tuple::{PyTuple, PyTupleRef},
|
||||
},
|
||||
bytecode,
|
||||
bytecode::{self, Instruction},
|
||||
convert::{IntoObject, ToPyResult},
|
||||
coroutine::Coro,
|
||||
exceptions::ExceptionCtor,
|
||||
@@ -24,6 +24,7 @@ use core::iter::zip;
|
||||
use core::sync::atomic;
|
||||
use indexmap::IndexMap;
|
||||
use itertools::Itertools;
|
||||
|
||||
use rustpython_common::{boxvec::BoxVec, lock::PyMutex, wtf8::Wtf8Buf};
|
||||
use rustpython_compiler_core::SourceLocation;
|
||||
|
||||
@@ -387,15 +388,15 @@ impl ExecutingFrame<'_> {
|
||||
}
|
||||
|
||||
// Check if this is a RERAISE instruction
|
||||
// Both Instruction::Raise { kind: Reraise/ReraiseFromStack } and
|
||||
// Instruction::Reraise are reraise operations that should not add
|
||||
// Both AnyInstruction::Raise { kind: Reraise/ReraiseFromStack } and
|
||||
// AnyInstruction::Reraise are reraise operations that should not add
|
||||
// new traceback entries
|
||||
let is_reraise = match op {
|
||||
bytecode::Instruction::RaiseVarargs { kind } => matches!(
|
||||
Instruction::RaiseVarargs { kind } => matches!(
|
||||
kind.get(arg),
|
||||
bytecode::RaiseKind::BareRaise | bytecode::RaiseKind::ReraiseFromStack
|
||||
),
|
||||
bytecode::Instruction::Reraise { .. } => true,
|
||||
Instruction::Reraise { .. } => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
@@ -439,13 +440,13 @@ impl ExecutingFrame<'_> {
|
||||
let lasti = self.lasti() as usize;
|
||||
if let Some(unit) = self.code.instructions.get(lasti) {
|
||||
match &unit.op {
|
||||
bytecode::Instruction::Send { .. } => return Some(self.top_value()),
|
||||
bytecode::Instruction::Resume { .. } => {
|
||||
Instruction::Send { .. } => return Some(self.top_value()),
|
||||
Instruction::Resume { .. } => {
|
||||
// Check if previous instruction was YIELD_VALUE with arg >= 1
|
||||
// This indicates yield-from/await context
|
||||
if lasti > 0
|
||||
&& let Some(prev_unit) = self.code.instructions.get(lasti - 1)
|
||||
&& let bytecode::Instruction::YieldValue { .. } = &prev_unit.op
|
||||
&& let Instruction::YieldValue { .. } = &prev_unit.op
|
||||
{
|
||||
// YIELD_VALUE arg: 0 = direct yield, >= 1 = yield-from/await
|
||||
// OpArgByte.0 is the raw byte value
|
||||
@@ -550,7 +551,7 @@ impl ExecutingFrame<'_> {
|
||||
#[inline(always)]
|
||||
fn execute_instruction(
|
||||
&mut self,
|
||||
instruction: bytecode::Instruction,
|
||||
instruction: Instruction,
|
||||
arg: bytecode::OpArg,
|
||||
extend_arg: &mut bool,
|
||||
vm: &VirtualMachine,
|
||||
@@ -584,7 +585,7 @@ impl ExecutingFrame<'_> {
|
||||
}
|
||||
|
||||
match instruction {
|
||||
bytecode::Instruction::BeforeAsyncWith => {
|
||||
Instruction::BeforeAsyncWith => {
|
||||
let mgr = self.pop_value();
|
||||
let error_string = || -> String {
|
||||
format!(
|
||||
@@ -609,8 +610,8 @@ impl ExecutingFrame<'_> {
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::BinaryOp { op } => self.execute_bin_op(vm, op.get(arg)),
|
||||
bytecode::Instruction::BinarySubscr => {
|
||||
Instruction::BinaryOp { op } => self.execute_bin_op(vm, op.get(arg)),
|
||||
Instruction::BinarySubscr => {
|
||||
let key = self.pop_value();
|
||||
let container = self.pop_value();
|
||||
let result = container.get_item(key.as_object(), vm)?;
|
||||
@@ -618,30 +619,30 @@ impl ExecutingFrame<'_> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
bytecode::Instruction::Break { target } => self.unwind_blocks(
|
||||
Instruction::Break { target } => self.unwind_blocks(
|
||||
vm,
|
||||
UnwindReason::Break {
|
||||
target: target.get(arg),
|
||||
},
|
||||
),
|
||||
bytecode::Instruction::BuildListFromTuples { size } => {
|
||||
Instruction::BuildListFromTuples { size } => {
|
||||
// SAFETY: compiler guarantees `size` tuples are on the stack
|
||||
let elements = unsafe { self.flatten_tuples(size.get(arg) as usize) };
|
||||
let list_obj = vm.ctx.new_list(elements);
|
||||
self.push_value(list_obj.into());
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::BuildList { size } => {
|
||||
Instruction::BuildList { size } => {
|
||||
let elements = self.pop_multiple(size.get(arg) as usize).collect();
|
||||
let list_obj = vm.ctx.new_list(elements);
|
||||
self.push_value(list_obj.into());
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::BuildMapForCall { size } => {
|
||||
Instruction::BuildMapForCall { size } => {
|
||||
self.execute_build_map_for_call(vm, size.get(arg))
|
||||
}
|
||||
bytecode::Instruction::BuildMap { size } => self.execute_build_map(vm, size.get(arg)),
|
||||
bytecode::Instruction::BuildSetFromTuples { size } => {
|
||||
Instruction::BuildMap { size } => self.execute_build_map(vm, size.get(arg)),
|
||||
Instruction::BuildSetFromTuples { size } => {
|
||||
let set = PySet::default().into_ref(&vm.ctx);
|
||||
for element in self.pop_multiple(size.get(arg) as usize) {
|
||||
// SAFETY: trust compiler
|
||||
@@ -653,7 +654,7 @@ impl ExecutingFrame<'_> {
|
||||
self.push_value(set.into());
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::BuildSet { size } => {
|
||||
Instruction::BuildSet { size } => {
|
||||
let set = PySet::default().into_ref(&vm.ctx);
|
||||
for element in self.pop_multiple(size.get(arg) as usize) {
|
||||
set.add(element, vm)?;
|
||||
@@ -661,11 +662,9 @@ impl ExecutingFrame<'_> {
|
||||
self.push_value(set.into());
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::BuildSlice { argc } => {
|
||||
self.execute_build_slice(vm, argc.get(arg))
|
||||
}
|
||||
Instruction::BuildSlice { argc } => self.execute_build_slice(vm, argc.get(arg)),
|
||||
/*
|
||||
bytecode::Instruction::ToBool => {
|
||||
Instruction::ToBool => {
|
||||
dbg!("Shouldn't be called outside of match statements for now")
|
||||
let value = self.pop_value();
|
||||
// call __bool__
|
||||
@@ -674,7 +673,7 @@ impl ExecutingFrame<'_> {
|
||||
Ok(None)
|
||||
}
|
||||
*/
|
||||
bytecode::Instruction::BuildString { size } => {
|
||||
Instruction::BuildString { size } => {
|
||||
let s: Wtf8Buf = self
|
||||
.pop_multiple(size.get(arg) as usize)
|
||||
.map(|pyobj| pyobj.downcast::<PyStr>().unwrap())
|
||||
@@ -682,7 +681,7 @@ impl ExecutingFrame<'_> {
|
||||
self.push_value(vm.ctx.new_str(s).into());
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::BuildTupleFromIter => {
|
||||
Instruction::BuildTupleFromIter => {
|
||||
if !self.top_value().class().is(vm.ctx.types.tuple_type) {
|
||||
let elements: Vec<_> = self.pop_value().try_to_value(vm)?;
|
||||
let list_obj = vm.ctx.new_tuple(elements);
|
||||
@@ -690,48 +689,48 @@ impl ExecutingFrame<'_> {
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::BuildTupleFromTuples { size } => {
|
||||
Instruction::BuildTupleFromTuples { size } => {
|
||||
// SAFETY: compiler guarantees `size` tuples are on the stack
|
||||
let elements = unsafe { self.flatten_tuples(size.get(arg) as usize) };
|
||||
let list_obj = vm.ctx.new_tuple(elements);
|
||||
self.push_value(list_obj.into());
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::BuildTuple { size } => {
|
||||
Instruction::BuildTuple { size } => {
|
||||
let elements = self.pop_multiple(size.get(arg) as usize).collect();
|
||||
let list_obj = vm.ctx.new_tuple(elements);
|
||||
self.push_value(list_obj.into());
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::Call { nargs } => {
|
||||
Instruction::Call { nargs } => {
|
||||
// Stack: [callable, self_or_null, arg1, ..., argN]
|
||||
let args = self.collect_positional_args(nargs.get(arg));
|
||||
self.execute_call(args, vm)
|
||||
}
|
||||
bytecode::Instruction::CallKw { nargs } => {
|
||||
Instruction::CallKw { nargs } => {
|
||||
// Stack: [callable, self_or_null, arg1, ..., argN, kwarg_names]
|
||||
let args = self.collect_keyword_args(nargs.get(arg));
|
||||
self.execute_call(args, vm)
|
||||
}
|
||||
bytecode::Instruction::CallFunctionEx { has_kwargs } => {
|
||||
Instruction::CallFunctionEx { has_kwargs } => {
|
||||
// Stack: [callable, self_or_null, args_tuple, (kwargs_dict)?]
|
||||
let args = self.collect_ex_args(vm, has_kwargs.get(arg))?;
|
||||
self.execute_call(args, vm)
|
||||
}
|
||||
bytecode::Instruction::CallIntrinsic1 { func } => {
|
||||
Instruction::CallIntrinsic1 { func } => {
|
||||
let value = self.pop_value();
|
||||
let result = self.call_intrinsic_1(func.get(arg), value, vm)?;
|
||||
self.push_value(result);
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::CallIntrinsic2 { func } => {
|
||||
Instruction::CallIntrinsic2 { func } => {
|
||||
let value2 = self.pop_value();
|
||||
let value1 = self.pop_value();
|
||||
let result = self.call_intrinsic_2(func.get(arg), value1, value2, vm)?;
|
||||
self.push_value(result);
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::CheckEgMatch => {
|
||||
Instruction::CheckEgMatch => {
|
||||
let match_type = self.pop_value();
|
||||
let exc_value = self.pop_value();
|
||||
let (rest, matched) =
|
||||
@@ -740,8 +739,8 @@ impl ExecutingFrame<'_> {
|
||||
self.push_value(matched);
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::CompareOp { op } => self.execute_compare(vm, op.get(arg)),
|
||||
bytecode::Instruction::ContainsOp(invert) => {
|
||||
Instruction::CompareOp { op } => self.execute_compare(vm, op.get(arg)),
|
||||
Instruction::ContainsOp(invert) => {
|
||||
let b = self.pop_value();
|
||||
let a = self.pop_value();
|
||||
|
||||
@@ -752,17 +751,17 @@ impl ExecutingFrame<'_> {
|
||||
self.push_value(vm.ctx.new_bool(value).into());
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::Continue { target } => self.unwind_blocks(
|
||||
Instruction::Continue { target } => self.unwind_blocks(
|
||||
vm,
|
||||
UnwindReason::Continue {
|
||||
target: target.get(arg),
|
||||
},
|
||||
),
|
||||
|
||||
bytecode::Instruction::ConvertValue { oparg: conversion } => {
|
||||
Instruction::ConvertValue { oparg: conversion } => {
|
||||
self.convert_value(conversion.get(arg), vm)
|
||||
}
|
||||
bytecode::Instruction::Copy { index } => {
|
||||
Instruction::Copy { index } => {
|
||||
// CopyItem { index: 1 } copies TOS
|
||||
// CopyItem { index: 2 } copies second from top
|
||||
// This is 1-indexed to match CPython
|
||||
@@ -778,12 +777,12 @@ impl ExecutingFrame<'_> {
|
||||
self.push_value_opt(value);
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::DeleteAttr { idx } => self.delete_attr(vm, idx.get(arg)),
|
||||
bytecode::Instruction::DeleteDeref(i) => {
|
||||
Instruction::DeleteAttr { idx } => self.delete_attr(vm, idx.get(arg)),
|
||||
Instruction::DeleteDeref(i) => {
|
||||
self.cells_frees[i.get(arg) as usize].set(None);
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::DeleteFast(idx) => {
|
||||
Instruction::DeleteFast(idx) => {
|
||||
let mut fastlocals = self.fastlocals.lock();
|
||||
let idx = idx.get(arg) as usize;
|
||||
if fastlocals[idx].is_none() {
|
||||
@@ -798,7 +797,7 @@ impl ExecutingFrame<'_> {
|
||||
fastlocals[idx] = None;
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::DeleteGlobal(idx) => {
|
||||
Instruction::DeleteGlobal(idx) => {
|
||||
let name = self.code.names[idx.get(arg) as usize];
|
||||
match self.globals.del_item(name, vm) {
|
||||
Ok(()) => {}
|
||||
@@ -809,7 +808,7 @@ impl ExecutingFrame<'_> {
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::DeleteName(idx) => {
|
||||
Instruction::DeleteName(idx) => {
|
||||
let name = self.code.names[idx.get(arg) as usize];
|
||||
let res = self.locals.mapping().ass_subscript(name, None, vm);
|
||||
|
||||
@@ -822,8 +821,8 @@ impl ExecutingFrame<'_> {
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::DeleteSubscr => self.execute_delete_subscript(vm),
|
||||
bytecode::Instruction::DictUpdate { index } => {
|
||||
Instruction::DeleteSubscr => self.execute_delete_subscript(vm),
|
||||
Instruction::DictUpdate { index } => {
|
||||
// Stack before: [..., dict, ..., source] (source at TOS)
|
||||
// Stack after: [..., dict, ...] (source consumed)
|
||||
// The dict to update is at position TOS-i (before popping source)
|
||||
@@ -859,7 +858,7 @@ impl ExecutingFrame<'_> {
|
||||
dict.merge_object(source, vm)?;
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::EndAsyncFor => {
|
||||
Instruction::EndAsyncFor => {
|
||||
// END_ASYNC_FOR pops (awaitable, exc) from stack
|
||||
// Stack: [awaitable, exc] -> []
|
||||
// exception_unwind pushes exception to stack before jumping to handler
|
||||
@@ -879,19 +878,19 @@ impl ExecutingFrame<'_> {
|
||||
Err(exc)
|
||||
}
|
||||
}
|
||||
bytecode::Instruction::ExtendedArg => {
|
||||
Instruction::ExtendedArg => {
|
||||
*extend_arg = true;
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::ForIter { target } => self.execute_for_iter(vm, target.get(arg)),
|
||||
bytecode::Instruction::FormatSimple => {
|
||||
Instruction::ForIter { target } => self.execute_for_iter(vm, target.get(arg)),
|
||||
Instruction::FormatSimple => {
|
||||
let value = self.pop_value();
|
||||
let formatted = vm.format(&value, vm.ctx.new_str(""))?;
|
||||
self.push_value(formatted.into());
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::FormatWithSpec => {
|
||||
Instruction::FormatWithSpec => {
|
||||
let spec = self.pop_value();
|
||||
let value = self.pop_value();
|
||||
let formatted = vm.format(&value, spec.downcast::<PyStr>().unwrap())?;
|
||||
@@ -899,13 +898,13 @@ impl ExecutingFrame<'_> {
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::GetAIter => {
|
||||
Instruction::GetAIter => {
|
||||
let aiterable = self.pop_value();
|
||||
let aiter = vm.call_special_method(&aiterable, identifier!(vm, __aiter__), ())?;
|
||||
self.push_value(aiter);
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::GetANext => {
|
||||
Instruction::GetANext => {
|
||||
#[cfg(debug_assertions)] // remove when GetANext is fully implemented
|
||||
let orig_stack_len = self.state.stack.len();
|
||||
|
||||
@@ -950,7 +949,7 @@ impl ExecutingFrame<'_> {
|
||||
debug_assert_eq!(orig_stack_len + 1, self.state.stack.len());
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::GetAwaitable => {
|
||||
Instruction::GetAwaitable => {
|
||||
use crate::protocol::PyIter;
|
||||
|
||||
let awaited_obj = self.pop_value();
|
||||
@@ -986,13 +985,13 @@ impl ExecutingFrame<'_> {
|
||||
self.push_value(awaitable);
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::GetIter => {
|
||||
Instruction::GetIter => {
|
||||
let iterated_obj = self.pop_value();
|
||||
let iter_obj = iterated_obj.get_iter(vm)?;
|
||||
self.push_value(iter_obj.into());
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::GetYieldFromIter => {
|
||||
Instruction::GetYieldFromIter => {
|
||||
// GET_YIELD_FROM_ITER: prepare iterator for yield from
|
||||
// If iterable is a coroutine, ensure we're in a coroutine context
|
||||
// If iterable is a generator, use it directly
|
||||
@@ -1017,23 +1016,23 @@ impl ExecutingFrame<'_> {
|
||||
self.push_value(iter);
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::GetLen => {
|
||||
Instruction::GetLen => {
|
||||
// STACK.append(len(STACK[-1]))
|
||||
let obj = self.top_value();
|
||||
let len = obj.length(vm)?;
|
||||
self.push_value(vm.ctx.new_int(len).into());
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::ImportFrom { idx } => {
|
||||
Instruction::ImportFrom { idx } => {
|
||||
let obj = self.import_from(vm, idx.get(arg))?;
|
||||
self.push_value(obj);
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::ImportName { idx } => {
|
||||
Instruction::ImportName { idx } => {
|
||||
self.import(vm, Some(self.code.names[idx.get(arg) as usize]))?;
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::IsOp(invert) => {
|
||||
Instruction::IsOp(invert) => {
|
||||
let b = self.pop_value();
|
||||
let a = self.pop_value();
|
||||
let res = a.is(&b);
|
||||
@@ -1045,10 +1044,10 @@ impl ExecutingFrame<'_> {
|
||||
self.push_value(vm.ctx.new_bool(value).into());
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::JumpIfFalseOrPop { target } => {
|
||||
Instruction::JumpIfFalseOrPop { target } => {
|
||||
self.jump_if_or_pop(vm, target.get(arg), false)
|
||||
}
|
||||
bytecode::Instruction::JumpIfNotExcMatch(target) => {
|
||||
Instruction::JumpIfNotExcMatch(target) => {
|
||||
let b = self.pop_value();
|
||||
let a = self.pop_value();
|
||||
if let Some(tuple_of_exceptions) = b.downcast_ref::<PyTuple>() {
|
||||
@@ -1071,26 +1070,22 @@ impl ExecutingFrame<'_> {
|
||||
self.push_value(vm.ctx.new_bool(value).into());
|
||||
self.pop_jump_if(vm, target.get(arg), false)
|
||||
}
|
||||
bytecode::Instruction::JumpIfTrueOrPop { target } => {
|
||||
Instruction::JumpIfTrueOrPop { target } => {
|
||||
self.jump_if_or_pop(vm, target.get(arg), true)
|
||||
}
|
||||
bytecode::Instruction::Jump { target } => {
|
||||
Instruction::JumpForward { target } => {
|
||||
self.jump(target.get(arg));
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::JumpForward { target } => {
|
||||
Instruction::JumpBackward { target } => {
|
||||
self.jump(target.get(arg));
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::JumpBackward { target } => {
|
||||
Instruction::JumpBackwardNoInterrupt { target } => {
|
||||
self.jump(target.get(arg));
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::JumpBackwardNoInterrupt { target } => {
|
||||
self.jump(target.get(arg));
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::ListAppend { i } => {
|
||||
Instruction::ListAppend { i } => {
|
||||
let item = self.pop_value();
|
||||
let obj = self.nth_value(i.get(arg));
|
||||
let list: &Py<PyList> = unsafe {
|
||||
@@ -1100,23 +1095,13 @@ impl ExecutingFrame<'_> {
|
||||
list.append(item);
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::LoadAttr { idx } => self.load_attr(vm, idx.get(arg)),
|
||||
bytecode::Instruction::LoadAttrMethod { .. } => {
|
||||
unreachable!("LoadAttrMethod is converted to LoadAttr during compilation")
|
||||
}
|
||||
bytecode::Instruction::LoadSuperAttr { arg: idx } => {
|
||||
self.load_super_attr(vm, idx.get(arg))
|
||||
}
|
||||
bytecode::Instruction::LoadSuperMethod { .. }
|
||||
| bytecode::Instruction::LoadZeroSuperAttr { .. }
|
||||
| bytecode::Instruction::LoadZeroSuperMethod { .. } => {
|
||||
unreachable!("LOAD_SUPER_* pseudo instructions are converted during compilation")
|
||||
}
|
||||
bytecode::Instruction::LoadBuildClass => {
|
||||
Instruction::LoadAttr { idx } => self.load_attr(vm, idx.get(arg)),
|
||||
Instruction::LoadSuperAttr { arg: idx } => self.load_super_attr(vm, idx.get(arg)),
|
||||
Instruction::LoadBuildClass => {
|
||||
self.push_value(vm.builtins.get_attr(identifier!(vm, __build_class__), vm)?);
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::LoadFromDictOrDeref(i) => {
|
||||
Instruction::LoadFromDictOrDeref(i) => {
|
||||
let i = i.get(arg) as usize;
|
||||
let name = if i < self.code.cellvars.len() {
|
||||
self.code.cellvars[i]
|
||||
@@ -1132,16 +1117,16 @@ impl ExecutingFrame<'_> {
|
||||
});
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::LoadClosure(i) => {
|
||||
Instruction::LoadClosure(i) => {
|
||||
let value = self.cells_frees[i.get(arg) as usize].clone();
|
||||
self.push_value(value.into());
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::LoadConst { idx } => {
|
||||
Instruction::LoadConst { idx } => {
|
||||
self.push_value(self.code.constants[idx.get(arg) as usize].clone().into());
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::LoadDeref(i) => {
|
||||
Instruction::LoadDeref(i) => {
|
||||
let i = i.get(arg) as usize;
|
||||
let x = self.cells_frees[i]
|
||||
.get()
|
||||
@@ -1149,7 +1134,7 @@ impl ExecutingFrame<'_> {
|
||||
self.push_value(x);
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::LoadFast(idx) => {
|
||||
Instruction::LoadFast(idx) => {
|
||||
#[cold]
|
||||
fn reference_error(
|
||||
varname: &'static PyStrInterned,
|
||||
@@ -1167,7 +1152,7 @@ impl ExecutingFrame<'_> {
|
||||
self.push_value(x);
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::LoadFastAndClear(idx) => {
|
||||
Instruction::LoadFastAndClear(idx) => {
|
||||
// Load value and clear the slot (for inlined comprehensions)
|
||||
// If slot is empty, push None (not an error - variable may not exist yet)
|
||||
let idx = idx.get(arg) as usize;
|
||||
@@ -1177,13 +1162,13 @@ impl ExecutingFrame<'_> {
|
||||
self.push_value(x);
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::LoadGlobal(idx) => {
|
||||
Instruction::LoadGlobal(idx) => {
|
||||
let name = &self.code.names[idx.get(arg) as usize];
|
||||
let x = self.load_global_or_builtin(name, vm)?;
|
||||
self.push_value(x);
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::LoadName(idx) => {
|
||||
Instruction::LoadName(idx) => {
|
||||
let name = self.code.names[idx.get(arg) as usize];
|
||||
let result = self.locals.mapping().subscript(name, vm);
|
||||
match result {
|
||||
@@ -1195,8 +1180,8 @@ impl ExecutingFrame<'_> {
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::MakeFunction => self.execute_make_function(vm),
|
||||
bytecode::Instruction::MapAdd { i } => {
|
||||
Instruction::MakeFunction => self.execute_make_function(vm),
|
||||
Instruction::MapAdd { i } => {
|
||||
let value = self.pop_value();
|
||||
let key = self.pop_value();
|
||||
let obj = self.nth_value(i.get(arg));
|
||||
@@ -1207,7 +1192,7 @@ impl ExecutingFrame<'_> {
|
||||
dict.set_item(&*key, value, vm)?;
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::MatchClass(nargs) => {
|
||||
Instruction::MatchClass(nargs) => {
|
||||
// STACK[-1] is a tuple of keyword attribute names, STACK[-2] is the class being matched against, and STACK[-3] is the match subject.
|
||||
// nargs is the number of positional sub-patterns.
|
||||
let kwd_attrs = self.pop_value();
|
||||
@@ -1328,7 +1313,7 @@ impl ExecutingFrame<'_> {
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::MatchKeys => {
|
||||
Instruction::MatchKeys => {
|
||||
// MATCH_KEYS doesn't pop subject and keys, only reads them
|
||||
let keys_tuple = self.top_value(); // stack[-1]
|
||||
let subject = self.nth_value(1); // stack[-2]
|
||||
@@ -1394,7 +1379,7 @@ impl ExecutingFrame<'_> {
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::MatchMapping => {
|
||||
Instruction::MatchMapping => {
|
||||
// Pop and push back the subject to keep it on stack
|
||||
let subject = self.pop_value();
|
||||
|
||||
@@ -1405,7 +1390,7 @@ impl ExecutingFrame<'_> {
|
||||
self.push_value(vm.ctx.new_bool(is_mapping).into());
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::MatchSequence => {
|
||||
Instruction::MatchSequence => {
|
||||
// Pop and push back the subject to keep it on stack
|
||||
let subject = self.pop_value();
|
||||
|
||||
@@ -1416,11 +1401,8 @@ impl ExecutingFrame<'_> {
|
||||
self.push_value(vm.ctx.new_bool(is_sequence).into());
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::Nop => Ok(None),
|
||||
bytecode::Instruction::PopBlock => {
|
||||
unreachable!("PopBlock is converted to Nop during compilation")
|
||||
}
|
||||
bytecode::Instruction::PopExcept => {
|
||||
Instruction::Nop => Ok(None),
|
||||
Instruction::PopExcept => {
|
||||
// Pop prev_exc from value stack and restore it
|
||||
let prev_exc = self.pop_value();
|
||||
if vm.is_none(&prev_exc) {
|
||||
@@ -1438,24 +1420,20 @@ impl ExecutingFrame<'_> {
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::PopJumpIfFalse { target } => {
|
||||
self.pop_jump_if(vm, target.get(arg), false)
|
||||
}
|
||||
bytecode::Instruction::PopJumpIfTrue { target } => {
|
||||
self.pop_jump_if(vm, target.get(arg), true)
|
||||
}
|
||||
bytecode::Instruction::PopTop => {
|
||||
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::PopTop => {
|
||||
// Pop value from stack and ignore.
|
||||
self.pop_value();
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::PushNull => {
|
||||
Instruction::PushNull => {
|
||||
// Push NULL for self_or_null slot in call protocol
|
||||
self.push_null();
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::RaiseVarargs { kind } => self.execute_raise(vm, kind.get(arg)),
|
||||
bytecode::Instruction::Resume { arg: resume_arg } => {
|
||||
Instruction::RaiseVarargs { kind } => self.execute_raise(vm, kind.get(arg)),
|
||||
Instruction::Resume { arg: resume_arg } => {
|
||||
// Resume execution after yield, await, or at function start
|
||||
// In CPython, this checks instrumentation and eval breaker
|
||||
// For now, we just check for signals/interrupts
|
||||
@@ -1467,15 +1445,15 @@ impl ExecutingFrame<'_> {
|
||||
// }
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::ReturnConst { idx } => {
|
||||
Instruction::ReturnConst { idx } => {
|
||||
let value = self.code.constants[idx.get(arg) as usize].clone().into();
|
||||
self.unwind_blocks(vm, UnwindReason::Returning { value })
|
||||
}
|
||||
bytecode::Instruction::ReturnValue => {
|
||||
Instruction::ReturnValue => {
|
||||
let value = self.pop_value();
|
||||
self.unwind_blocks(vm, UnwindReason::Returning { value })
|
||||
}
|
||||
bytecode::Instruction::SetAdd { i } => {
|
||||
Instruction::SetAdd { i } => {
|
||||
let item = self.pop_value();
|
||||
let obj = self.nth_value(i.get(arg));
|
||||
let set: &Py<PySet> = unsafe {
|
||||
@@ -1485,7 +1463,7 @@ impl ExecutingFrame<'_> {
|
||||
set.add(item, vm)?;
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::SetExcInfo => {
|
||||
Instruction::SetExcInfo => {
|
||||
// Set the current exception to TOS (for except* handlers)
|
||||
// This updates sys.exc_info() so bare 'raise' will reraise the matched exception
|
||||
let exc = self.top_value();
|
||||
@@ -1494,7 +1472,7 @@ impl ExecutingFrame<'_> {
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::PushExcInfo => {
|
||||
Instruction::PushExcInfo => {
|
||||
// Stack: [exc] -> [prev_exc, exc]
|
||||
let exc = self.pop_value();
|
||||
let prev_exc = vm
|
||||
@@ -1511,7 +1489,7 @@ impl ExecutingFrame<'_> {
|
||||
self.push_value(exc);
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::CheckExcMatch => {
|
||||
Instruction::CheckExcMatch => {
|
||||
// Stack: [exc, type] -> [exc, bool]
|
||||
let exc_type = self.pop_value();
|
||||
let exc = self.top_value();
|
||||
@@ -1521,7 +1499,7 @@ impl ExecutingFrame<'_> {
|
||||
self.push_value(vm.ctx.new_bool(result).into());
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::Reraise { depth } => {
|
||||
Instruction::Reraise { depth } => {
|
||||
// inst(RERAISE, (values[oparg], exc -- values[oparg]))
|
||||
//
|
||||
// Stack layout: [values..., exc] where len(values) == oparg
|
||||
@@ -1548,11 +1526,11 @@ impl ExecutingFrame<'_> {
|
||||
Err(exc)
|
||||
}
|
||||
}
|
||||
bytecode::Instruction::SetFunctionAttribute { attr } => {
|
||||
Instruction::SetFunctionAttribute { attr } => {
|
||||
self.execute_set_function_attribute(vm, attr.get(arg))
|
||||
}
|
||||
bytecode::Instruction::SetupAnnotations => self.setup_annotations(vm),
|
||||
bytecode::Instruction::BeforeWith => {
|
||||
Instruction::SetupAnnotations => self.setup_annotations(vm),
|
||||
Instruction::BeforeWith => {
|
||||
// TOS: context_manager
|
||||
// Result: [..., __exit__, __enter__ result]
|
||||
let context_manager = self.pop_value();
|
||||
@@ -1581,18 +1559,18 @@ impl ExecutingFrame<'_> {
|
||||
self.push_value(enter_res);
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::StoreAttr { idx } => self.store_attr(vm, idx.get(arg)),
|
||||
bytecode::Instruction::StoreDeref(i) => {
|
||||
Instruction::StoreAttr { idx } => self.store_attr(vm, idx.get(arg)),
|
||||
Instruction::StoreDeref(i) => {
|
||||
let value = self.pop_value();
|
||||
self.cells_frees[i.get(arg) as usize].set(Some(value));
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::StoreFast(idx) => {
|
||||
Instruction::StoreFast(idx) => {
|
||||
let value = self.pop_value();
|
||||
self.fastlocals.lock()[idx.get(arg) as usize] = Some(value);
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::StoreFastLoadFast {
|
||||
Instruction::StoreFastLoadFast {
|
||||
store_idx,
|
||||
load_idx,
|
||||
} => {
|
||||
@@ -1607,21 +1585,21 @@ impl ExecutingFrame<'_> {
|
||||
self.push_value(load_value);
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::StoreGlobal(idx) => {
|
||||
Instruction::StoreGlobal(idx) => {
|
||||
let value = self.pop_value();
|
||||
self.globals
|
||||
.set_item(self.code.names[idx.get(arg) as usize], value, vm)?;
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::StoreName(idx) => {
|
||||
Instruction::StoreName(idx) => {
|
||||
let name = self.code.names[idx.get(arg) as usize];
|
||||
let value = self.pop_value();
|
||||
self.locals.mapping().ass_subscript(name, Some(value), vm)?;
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::StoreSubscr => self.execute_store_subscript(vm),
|
||||
bytecode::Instruction::Subscript => self.execute_subscript(vm),
|
||||
bytecode::Instruction::Swap { index } => {
|
||||
Instruction::StoreSubscr => self.execute_store_subscript(vm),
|
||||
Instruction::Subscript => self.execute_subscript(vm),
|
||||
Instruction::Swap { index } => {
|
||||
let len = self.state.stack.len();
|
||||
debug_assert!(len > 0, "stack underflow in SWAP");
|
||||
let i = len - 1; // TOS index
|
||||
@@ -1638,20 +1616,18 @@ impl ExecutingFrame<'_> {
|
||||
self.state.stack.swap(i, j);
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::ToBool => {
|
||||
Instruction::ToBool => {
|
||||
let obj = self.pop_value();
|
||||
let bool_val = obj.try_to_bool(vm)?;
|
||||
self.push_value(vm.ctx.new_bool(bool_val).into());
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::UnpackEx { args } => {
|
||||
Instruction::UnpackEx { args } => {
|
||||
let args = args.get(arg);
|
||||
self.execute_unpack_ex(vm, args.before, args.after)
|
||||
}
|
||||
bytecode::Instruction::UnpackSequence { size } => {
|
||||
self.unpack_sequence(size.get(arg), vm)
|
||||
}
|
||||
bytecode::Instruction::WithExceptStart => {
|
||||
Instruction::UnpackSequence { size } => self.unpack_sequence(size.get(arg), vm),
|
||||
Instruction::WithExceptStart => {
|
||||
// Stack: [..., __exit__, lasti, prev_exc, exc]
|
||||
// Call __exit__(type, value, tb) and push result
|
||||
// __exit__ is at TOS-3 (below lasti, prev_exc, and exc)
|
||||
@@ -1674,7 +1650,7 @@ impl ExecutingFrame<'_> {
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::YieldValue { arg: oparg } => {
|
||||
Instruction::YieldValue { arg: oparg } => {
|
||||
let value = self.pop_value();
|
||||
// arg=0: direct yield (wrapped for async generators)
|
||||
// arg=1: yield from await/yield-from (NOT wrapped)
|
||||
@@ -1686,7 +1662,7 @@ impl ExecutingFrame<'_> {
|
||||
};
|
||||
Ok(Some(ExecutionResult::Yield(value)))
|
||||
}
|
||||
bytecode::Instruction::Send { target } => {
|
||||
Instruction::Send { target } => {
|
||||
// Stack: (receiver, value) -> (receiver, retval)
|
||||
// On StopIteration: replace value with stop value and jump to target
|
||||
let exit_label = target.get(arg);
|
||||
@@ -1710,7 +1686,7 @@ impl ExecutingFrame<'_> {
|
||||
}
|
||||
}
|
||||
}
|
||||
bytecode::Instruction::EndSend => {
|
||||
Instruction::EndSend => {
|
||||
// Stack: (receiver, value) -> (value)
|
||||
// Pops receiver, leaves value
|
||||
let value = self.pop_value();
|
||||
@@ -1718,7 +1694,7 @@ impl ExecutingFrame<'_> {
|
||||
self.push_value(value);
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::CleanupThrow => {
|
||||
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)
|
||||
// Otherwise: pop all 3, return Err(exc) for unwind_blocks to handle
|
||||
@@ -1756,19 +1732,19 @@ impl ExecutingFrame<'_> {
|
||||
.map_err(|_| vm.new_type_error("exception expected".to_owned()))?;
|
||||
Err(exc)
|
||||
}
|
||||
bytecode::Instruction::UnaryInvert => {
|
||||
Instruction::UnaryInvert => {
|
||||
let a = self.pop_value();
|
||||
let value = vm._invert(&a)?;
|
||||
self.push_value(value);
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::UnaryNegative => {
|
||||
Instruction::UnaryNegative => {
|
||||
let a = self.pop_value();
|
||||
let value = vm._neg(&a)?;
|
||||
self.push_value(value);
|
||||
Ok(None)
|
||||
}
|
||||
bytecode::Instruction::UnaryNot => {
|
||||
Instruction::UnaryNot => {
|
||||
let obj = self.pop_value();
|
||||
let value = obj.try_to_bool(vm)?;
|
||||
self.push_value(vm.ctx.new_bool(!value).into());
|
||||
|
||||
@@ -37,10 +37,10 @@ class Opcode(typing.NamedTuple):
|
||||
return self.id < other.id
|
||||
|
||||
|
||||
def extract_enum_body(contents: str) -> str:
|
||||
res = re.search(r"pub enum Instruction \{(.+?)\n\}", contents, re.DOTALL)
|
||||
def extract_enum_body(contents: str, enum_name: str) -> str:
|
||||
res = re.search(f"pub enum {enum_name} " + r"\{(.+?)\n\}", contents, re.DOTALL)
|
||||
if not res:
|
||||
raise ValueError("Could not find Instruction enum")
|
||||
raise ValueError(f"Could not find {enum_name} enum")
|
||||
|
||||
return "\n".join(
|
||||
line.split("//")[0].strip() # Remove any comment. i.e. "foo // some comment"
|
||||
@@ -50,7 +50,10 @@ def extract_enum_body(contents: str) -> str:
|
||||
|
||||
|
||||
contents = BYTECODE_FILE.read_text()
|
||||
enum_body = extract_enum_body(contents)
|
||||
enum_body = "\n".join(
|
||||
extract_enum_body(contents, enum_name)
|
||||
for enum_name in ("Instruction", "PseudoInstruction")
|
||||
)
|
||||
opcodes = list(Opcode.from_str(enum_body))
|
||||
|
||||
# Generate the output file
|
||||
|
||||
Reference in new issue
Block a user