Bytecode pseudo opcodes (#6715)

This commit is contained in:
Shahar Naveh authored and GitHub committed 2026-01-15 02:52:25 +09:00
1 parent 13a875f609
commit e3890f9b4a
11 files changed
+652 -416

No files matched your search

+12 -6
View File
@@ -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
-1
View File
@@ -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)
+91 -69
View File
@@ -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
View File
@@ -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();
+5 -15
View File
@@ -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)]
+298 -99
View File
@@ -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 {
+1 -1
View File
@@ -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)
+1 -7
View File
@@ -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
View File
@@ -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
View File
@@ -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());
+7 -4
View File
@@ -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