mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
Move Instruction enum to its own file (#6693)
* Move `Instruction` to its own file * Fix codegen and frame.rs * Adjust script
This commit is contained in:
1 parent
3909b18eac
commit
440b8de763
5 files changed
+862
-828
No files matched your search
@@ -1382,7 +1382,7 @@ impl Compiler {
|
||||
|
||||
if let Stmt::Expr(StmtExpr { value, .. }) = &last {
|
||||
self.compile_expression(value)?;
|
||||
emit!(self, Instruction::CopyItem { index: 1_u32 });
|
||||
emit!(self, Instruction::Copy { index: 1_u32 });
|
||||
emit!(
|
||||
self,
|
||||
Instruction::CallIntrinsic1 {
|
||||
@@ -1420,7 +1420,7 @@ impl Compiler {
|
||||
Stmt::FunctionDef(_) | Stmt::ClassDef(_) => {
|
||||
let pop_instructions = self.current_block().instructions.pop();
|
||||
let store_inst = compiler_unwrap_option(self, pop_instructions); // pop Instruction::Store
|
||||
emit!(self, Instruction::CopyItem { index: 1_u32 });
|
||||
emit!(self, Instruction::Copy { index: 1_u32 });
|
||||
self.current_block().instructions.push(store_inst);
|
||||
}
|
||||
_ => self.emit_load_const(ConstantData::None),
|
||||
@@ -1938,7 +1938,7 @@ impl Compiler {
|
||||
|
||||
for (i, target) in targets.iter().enumerate() {
|
||||
if i + 1 != targets.len() {
|
||||
emit!(self, Instruction::CopyItem { index: 1_u32 });
|
||||
emit!(self, Instruction::Copy { index: 1_u32 });
|
||||
}
|
||||
self.compile_store(target)?;
|
||||
}
|
||||
@@ -2205,7 +2205,7 @@ impl Compiler {
|
||||
);
|
||||
}
|
||||
|
||||
emit!(self, Instruction::CopyItem { index: 1_u32 });
|
||||
emit!(self, Instruction::Copy { index: 1_u32 });
|
||||
self.store_name(name.as_ref())?;
|
||||
}
|
||||
TypeParam::ParamSpec(TypeParamParamSpec { name, default, .. }) => {
|
||||
@@ -2230,7 +2230,7 @@ impl Compiler {
|
||||
);
|
||||
}
|
||||
|
||||
emit!(self, Instruction::CopyItem { index: 1_u32 });
|
||||
emit!(self, Instruction::Copy { index: 1_u32 });
|
||||
self.store_name(name.as_ref())?;
|
||||
}
|
||||
TypeParam::TypeVarTuple(TypeParamTypeVarTuple { name, default, .. }) => {
|
||||
@@ -2256,7 +2256,7 @@ impl Compiler {
|
||||
);
|
||||
}
|
||||
|
||||
emit!(self, Instruction::CopyItem { index: 1_u32 });
|
||||
emit!(self, Instruction::Copy { index: 1_u32 });
|
||||
self.store_name(name.as_ref())?;
|
||||
}
|
||||
};
|
||||
@@ -2396,7 +2396,7 @@ impl Compiler {
|
||||
|
||||
if let Some(cleanup) = finally_cleanup_block {
|
||||
self.switch_to_block(cleanup);
|
||||
emit!(self, Instruction::CopyItem { index: 3_u32 });
|
||||
emit!(self, Instruction::Copy { index: 3_u32 });
|
||||
emit!(self, Instruction::PopExcept);
|
||||
emit!(
|
||||
self,
|
||||
@@ -2457,7 +2457,7 @@ impl Compiler {
|
||||
// check if this handler can handle the exception:
|
||||
if let Some(exc_type) = type_ {
|
||||
// Duplicate exception for test:
|
||||
emit!(self, Instruction::CopyItem { index: 1_u32 });
|
||||
emit!(self, Instruction::Copy { index: 1_u32 });
|
||||
|
||||
// Check exception type:
|
||||
self.compile_expression(exc_type)?;
|
||||
@@ -2598,7 +2598,7 @@ impl Compiler {
|
||||
// POP_EXCEPT: pop prev_exc from stack and restore -> [prev_exc, lasti, exc]
|
||||
// RERAISE 1: reraise with lasti
|
||||
self.switch_to_block(cleanup_block);
|
||||
emit!(self, Instruction::CopyItem { index: 3_u32 });
|
||||
emit!(self, Instruction::Copy { index: 3_u32 });
|
||||
emit!(self, Instruction::PopExcept);
|
||||
emit!(
|
||||
self,
|
||||
@@ -2693,7 +2693,7 @@ impl Compiler {
|
||||
if let Some(cleanup) = finally_cleanup_block {
|
||||
self.switch_to_block(cleanup);
|
||||
// COPY 3: copy the exception from position 3
|
||||
emit!(self, Instruction::CopyItem { index: 3_u32 });
|
||||
emit!(self, Instruction::Copy { index: 3_u32 });
|
||||
// POP_EXCEPT: restore prev_exc as current exception
|
||||
emit!(self, Instruction::PopExcept);
|
||||
// RERAISE 1: reraise with lasti from stack
|
||||
@@ -2787,7 +2787,7 @@ impl Compiler {
|
||||
emit!(self, Instruction::BuildList { size: 0 });
|
||||
// Stack: [prev_exc, exc, []]
|
||||
// ADDOP_I(c, loc, COPY, 2);
|
||||
emit!(self, Instruction::CopyItem { index: 2 });
|
||||
emit!(self, Instruction::Copy { index: 2 });
|
||||
// Stack: [prev_exc, exc, [], exc_copy]
|
||||
// Now stack is: [prev_exc, orig, list, rest]
|
||||
}
|
||||
@@ -2817,7 +2817,7 @@ impl Compiler {
|
||||
|
||||
// ADDOP_I(c, loc, COPY, 1);
|
||||
// ADDOP_JUMP(c, loc, POP_JUMP_IF_NONE, no_match);
|
||||
emit!(self, Instruction::CopyItem { index: 1 });
|
||||
emit!(self, Instruction::Copy { index: 1 });
|
||||
self.emit_load_const(ConstantData::None);
|
||||
emit!(self, Instruction::IsOp(bytecode::Invert::No)); // is None?
|
||||
emit!(
|
||||
@@ -2947,7 +2947,7 @@ impl Compiler {
|
||||
// Stack: [prev_exc, result]
|
||||
|
||||
// COPY 1
|
||||
emit!(self, Instruction::CopyItem { index: 1 });
|
||||
emit!(self, Instruction::Copy { index: 1 });
|
||||
// Stack: [prev_exc, result, result]
|
||||
|
||||
// POP_JUMP_IF_NOT_NONE reraise
|
||||
@@ -3149,7 +3149,7 @@ impl Compiler {
|
||||
|
||||
// Handle docstring if present
|
||||
if let Some(doc) = doc_str {
|
||||
emit!(self, Instruction::CopyItem { index: 1_u32 });
|
||||
emit!(self, Instruction::Copy { index: 1_u32 });
|
||||
self.emit_load_const(ConstantData::Str {
|
||||
value: doc.to_string().into(),
|
||||
});
|
||||
@@ -3633,7 +3633,7 @@ impl Compiler {
|
||||
|
||||
if let Some(classcell_idx) = classcell_idx {
|
||||
emit!(self, Instruction::LoadClosure(classcell_idx.to_u32()));
|
||||
emit!(self, Instruction::CopyItem { index: 1_u32 });
|
||||
emit!(self, Instruction::Copy { index: 1_u32 });
|
||||
let classcell = self.name("__classcell__");
|
||||
emit!(self, Instruction::StoreName(classcell));
|
||||
} else {
|
||||
@@ -4081,7 +4081,7 @@ impl Compiler {
|
||||
// to be in the exception table for these instructions.
|
||||
// If we cleared fblock, exceptions here would propagate uncaught.
|
||||
self.switch_to_block(cleanup_block);
|
||||
emit!(self, Instruction::CopyItem { index: 3 });
|
||||
emit!(self, Instruction::Copy { index: 3 });
|
||||
emit!(self, Instruction::PopExcept);
|
||||
emit!(self, Instruction::Reraise { depth: 1 });
|
||||
|
||||
@@ -4384,7 +4384,7 @@ impl Compiler {
|
||||
continue;
|
||||
}
|
||||
// Duplicate the subject.
|
||||
emit!(self, Instruction::CopyItem { index: 1_u32 });
|
||||
emit!(self, Instruction::Copy { index: 1_u32 });
|
||||
if i < star {
|
||||
// For indices before the star, use a nonnegative index equal to i.
|
||||
self.emit_load_const(ConstantData::Integer { value: i.into() });
|
||||
@@ -4457,7 +4457,7 @@ impl Compiler {
|
||||
|
||||
// Otherwise, there is a sub-pattern. Duplicate the object on top of the stack.
|
||||
pc.on_top += 1;
|
||||
emit!(self, Instruction::CopyItem { index: 1_u32 });
|
||||
emit!(self, Instruction::Copy { index: 1_u32 });
|
||||
// Compile the sub-pattern.
|
||||
self.compile_pattern(p.pattern.as_ref().unwrap(), pc)?;
|
||||
// After success, decrement the on_top counter.
|
||||
@@ -4558,7 +4558,7 @@ impl Compiler {
|
||||
// 2. Emit MATCH_CLASS with nargs.
|
||||
emit!(self, Instruction::MatchClass(u32::try_from(nargs).unwrap()));
|
||||
// 3. Duplicate the top of the stack.
|
||||
emit!(self, Instruction::CopyItem { index: 1_u32 });
|
||||
emit!(self, Instruction::Copy { index: 1_u32 });
|
||||
// 4. Load None.
|
||||
self.emit_load_const(ConstantData::None);
|
||||
// 5. Compare with IS_OP 1.
|
||||
@@ -4723,7 +4723,7 @@ impl Compiler {
|
||||
pc.on_top += 2; // subject and keys_tuple are underneath
|
||||
|
||||
// Check if match succeeded
|
||||
emit!(self, Instruction::CopyItem { index: 1_u32 });
|
||||
emit!(self, Instruction::Copy { index: 1_u32 });
|
||||
// Stack: [subject, keys_tuple, values_tuple, values_tuple_copy]
|
||||
|
||||
// Check if copy is None (consumes the copy like POP_JUMP_IF_NONE)
|
||||
@@ -4776,7 +4776,7 @@ impl Compiler {
|
||||
// Copy rest_dict which is at position (1 + remaining) from TOS
|
||||
emit!(
|
||||
self,
|
||||
Instruction::CopyItem {
|
||||
Instruction::Copy {
|
||||
index: 1 + remaining
|
||||
}
|
||||
);
|
||||
@@ -4833,7 +4833,7 @@ impl Compiler {
|
||||
pc.fail_pop.clear();
|
||||
pc.on_top = 0;
|
||||
// Emit a COPY(1) instruction before compiling the alternative.
|
||||
emit!(self, Instruction::CopyItem { index: 1_u32 });
|
||||
emit!(self, Instruction::Copy { index: 1_u32 });
|
||||
self.compile_pattern(alt, pc)?;
|
||||
|
||||
let n_stores = pc.stores.len();
|
||||
@@ -5080,7 +5080,7 @@ impl Compiler {
|
||||
for (i, m) in cases.iter().enumerate().take(case_count) {
|
||||
// Only copy the subject if not on the last case
|
||||
if i != case_count - 1 {
|
||||
emit!(self, Instruction::CopyItem { index: 1_u32 });
|
||||
emit!(self, Instruction::Copy { index: 1_u32 });
|
||||
}
|
||||
|
||||
pattern_context.stores = Vec::with_capacity(1);
|
||||
@@ -5127,7 +5127,7 @@ impl Compiler {
|
||||
if let Some(ref guard) = m.guard {
|
||||
// Compile guard and jump to end if false
|
||||
self.compile_expression(guard)?;
|
||||
emit!(self, Instruction::CopyItem { index: 1_u32 });
|
||||
emit!(self, Instruction::Copy { index: 1_u32 });
|
||||
emit!(self, Instruction::PopJumpIfFalse { target: end });
|
||||
emit!(self, Instruction::PopTop);
|
||||
}
|
||||
@@ -5207,13 +5207,13 @@ impl Compiler {
|
||||
|
||||
// store rhs for the next comparison in chain
|
||||
emit!(self, Instruction::Swap { index: 2 });
|
||||
emit!(self, Instruction::CopyItem { index: 2 });
|
||||
emit!(self, Instruction::Copy { index: 2 });
|
||||
|
||||
self.compile_addcompare(op);
|
||||
|
||||
// if comparison result is false, we break with this value; if true, try the next one.
|
||||
/*
|
||||
emit!(self, Instruction::CopyItem { index: 1 });
|
||||
emit!(self, Instruction::Copy { index: 1 });
|
||||
// emit!(self, Instruction::ToBool); // TODO: Uncomment this
|
||||
emit!(self, Instruction::PopJumpIfFalse { target: cleanup });
|
||||
emit!(self, Instruction::PopTop);
|
||||
@@ -5398,8 +5398,8 @@ impl Compiler {
|
||||
// But we can't use compile_subscript directly because we need DUP_TOP2
|
||||
self.compile_expression(value)?;
|
||||
self.compile_expression(slice)?;
|
||||
emit!(self, Instruction::CopyItem { index: 2_u32 });
|
||||
emit!(self, Instruction::CopyItem { index: 2_u32 });
|
||||
emit!(self, Instruction::Copy { index: 2_u32 });
|
||||
emit!(self, Instruction::Copy { index: 2_u32 });
|
||||
emit!(self, Instruction::Subscript);
|
||||
AugAssignKind::Subscript
|
||||
}
|
||||
@@ -5407,7 +5407,7 @@ impl Compiler {
|
||||
let attr = attr.as_str();
|
||||
self.check_forbidden_name(attr, NameUsage::Store)?;
|
||||
self.compile_expression(value)?;
|
||||
emit!(self, Instruction::CopyItem { index: 1_u32 });
|
||||
emit!(self, Instruction::Copy { index: 1_u32 });
|
||||
let idx = self.name(attr);
|
||||
emit!(self, Instruction::LoadAttr { idx });
|
||||
AugAssignKind::Attr { idx }
|
||||
@@ -5564,7 +5564,7 @@ impl Compiler {
|
||||
for value in values {
|
||||
self.compile_expression(value)?;
|
||||
|
||||
emit!(self, Instruction::CopyItem { index: 1_u32 });
|
||||
emit!(self, Instruction::Copy { index: 1_u32 });
|
||||
match op {
|
||||
BoolOp::And => {
|
||||
emit!(
|
||||
@@ -6062,7 +6062,7 @@ impl Compiler {
|
||||
range: _,
|
||||
}) => {
|
||||
self.compile_expression(value)?;
|
||||
emit!(self, Instruction::CopyItem { index: 1_u32 });
|
||||
emit!(self, Instruction::Copy { index: 1_u32 });
|
||||
self.compile_store(target)?;
|
||||
}
|
||||
Expr::FString(fstring) => {
|
||||
|
||||
@@ -14,6 +14,10 @@ use malachite_bigint::BigInt;
|
||||
use num_complex::Complex64;
|
||||
use rustpython_wtf8::{Wtf8, Wtf8Buf};
|
||||
|
||||
pub use crate::bytecode::instruction::Instruction;
|
||||
|
||||
mod instruction;
|
||||
|
||||
/// Exception table entry for zero-cost exception handling
|
||||
/// Format: (start, size, target, depth<<1|lasti)
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
@@ -670,354 +674,6 @@ op_arg_enum!(
|
||||
|
||||
pub type NameIdx = u32;
|
||||
|
||||
/// 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)]
|
||||
#[repr(u8)]
|
||||
pub enum Instruction {
|
||||
// ==================== No-argument instructions (opcode < 44) ====================
|
||||
Cache = 0, // Placeholder
|
||||
BeforeAsyncWith = 1,
|
||||
BeforeWith = 2,
|
||||
BinaryOpInplaceAddUnicode = 3, // Placeholder
|
||||
BinarySlice = 4, // Placeholder
|
||||
BinarySubscr = 5,
|
||||
CheckEgMatch = 6,
|
||||
CheckExcMatch = 7,
|
||||
CleanupThrow = 8,
|
||||
DeleteSubscr = 9,
|
||||
EndAsyncFor = 10,
|
||||
EndFor = 11, // Placeholder
|
||||
EndSend = 12,
|
||||
ExitInitCheck = 13, // Placeholder
|
||||
FormatSimple = 14,
|
||||
FormatWithSpec = 15,
|
||||
GetAIter = 16,
|
||||
Reserved = 17,
|
||||
GetANext = 18,
|
||||
GetIter = 19,
|
||||
GetLen = 20,
|
||||
GetYieldFromIter = 21,
|
||||
InterpreterExit = 22, // Placeholder
|
||||
LoadAssertionError = 23, // Placeholder
|
||||
LoadBuildClass = 24,
|
||||
LoadLocals = 25, // Placeholder
|
||||
MakeFunction = 26,
|
||||
MatchKeys = 27,
|
||||
MatchMapping = 28,
|
||||
MatchSequence = 29,
|
||||
Nop = 30,
|
||||
PopExcept = 31,
|
||||
PopTop = 32,
|
||||
PushExcInfo = 33,
|
||||
PushNull = 34, // Placeholder
|
||||
ReturnGenerator = 35, // Placeholder
|
||||
ReturnValue = 36,
|
||||
SetupAnnotations = 37,
|
||||
StoreSlice = 38, // Placeholder
|
||||
StoreSubscr = 39,
|
||||
ToBool = 40,
|
||||
UnaryInvert = 41,
|
||||
UnaryNegative = 42,
|
||||
UnaryNot = 43,
|
||||
WithExceptStart = 44,
|
||||
// ==================== With-argument instructions (opcode > 44) ====================
|
||||
BinaryOp {
|
||||
op: Arg<BinaryOperator>,
|
||||
} = 45,
|
||||
BuildConstKeyMap {
|
||||
size: Arg<u32>,
|
||||
} = 46, // Placeholder
|
||||
BuildList {
|
||||
size: Arg<u32>,
|
||||
} = 47,
|
||||
BuildMap {
|
||||
size: Arg<u32>,
|
||||
} = 48,
|
||||
BuildSet {
|
||||
size: Arg<u32>,
|
||||
} = 49,
|
||||
BuildSlice {
|
||||
argc: Arg<BuildSliceArgCount>,
|
||||
} = 50,
|
||||
BuildString {
|
||||
size: Arg<u32>,
|
||||
} = 51,
|
||||
BuildTuple {
|
||||
size: Arg<u32>,
|
||||
} = 52,
|
||||
Call {
|
||||
nargs: Arg<u32>,
|
||||
} = 53,
|
||||
CallFunctionEx {
|
||||
has_kwargs: Arg<bool>,
|
||||
} = 54,
|
||||
CallIntrinsic1 {
|
||||
func: Arg<IntrinsicFunction1>,
|
||||
} = 55,
|
||||
CallIntrinsic2 {
|
||||
func: Arg<IntrinsicFunction2>,
|
||||
} = 56,
|
||||
CallKw {
|
||||
nargs: Arg<u32>,
|
||||
} = 57,
|
||||
CompareOp {
|
||||
op: Arg<ComparisonOperator>,
|
||||
} = 58,
|
||||
ContainsOp(Arg<Invert>) = 59,
|
||||
ConvertValue {
|
||||
oparg: Arg<ConvertValueOparg>,
|
||||
} = 60,
|
||||
CopyItem {
|
||||
index: Arg<u32>,
|
||||
} = 61,
|
||||
CopyFreeVars {
|
||||
count: Arg<u32>,
|
||||
} = 62, // Placeholder
|
||||
DeleteAttr {
|
||||
idx: Arg<NameIdx>,
|
||||
} = 63,
|
||||
DeleteDeref(Arg<NameIdx>) = 64,
|
||||
DeleteFast(Arg<NameIdx>) = 65,
|
||||
DeleteGlobal(Arg<NameIdx>) = 66,
|
||||
DeleteName(Arg<NameIdx>) = 67,
|
||||
DictMerge {
|
||||
index: Arg<u32>,
|
||||
} = 68, // Placeholder
|
||||
DictUpdate {
|
||||
index: Arg<u32>,
|
||||
} = 69,
|
||||
EnterExecutor = 70, // Placeholder
|
||||
ExtendedArg = 71,
|
||||
ForIter {
|
||||
target: Arg<Label>,
|
||||
} = 72,
|
||||
GetAwaitable = 73, // TODO: Make this instruction to hold an oparg
|
||||
ImportFrom {
|
||||
idx: Arg<NameIdx>,
|
||||
} = 74,
|
||||
ImportName {
|
||||
idx: Arg<NameIdx>,
|
||||
} = 75,
|
||||
IsOp(Arg<Invert>) = 76,
|
||||
JumpBackward {
|
||||
target: Arg<Label>,
|
||||
} = 77, // Placeholder
|
||||
JumpBackwardNoInterrupt {
|
||||
target: Arg<Label>,
|
||||
} = 78, // Placeholder
|
||||
JumpForward {
|
||||
target: Arg<Label>,
|
||||
} = 79, // Placeholder
|
||||
ListAppend {
|
||||
i: Arg<u32>,
|
||||
} = 80,
|
||||
ListExtend {
|
||||
i: Arg<u32>,
|
||||
} = 81, // Placeholder
|
||||
LoadAttr {
|
||||
idx: Arg<NameIdx>,
|
||||
} = 82,
|
||||
LoadConst {
|
||||
idx: Arg<u32>,
|
||||
} = 83,
|
||||
LoadDeref(Arg<NameIdx>) = 84,
|
||||
LoadFast(Arg<NameIdx>) = 85,
|
||||
LoadFastAndClear(Arg<NameIdx>) = 86,
|
||||
LoadFastCheck(Arg<NameIdx>) = 87, // Placeholder
|
||||
LoadFastLoadFast {
|
||||
arg: Arg<u32>,
|
||||
} = 88, // Placeholder
|
||||
LoadFromDictOrDeref(Arg<NameIdx>) = 89,
|
||||
LoadFromDictOrGlobals(Arg<NameIdx>) = 90, // Placeholder
|
||||
LoadGlobal(Arg<NameIdx>) = 91,
|
||||
LoadName(Arg<NameIdx>) = 92,
|
||||
LoadSuperAttr {
|
||||
arg: Arg<u32>,
|
||||
} = 93, // Placeholder
|
||||
MakeCell(Arg<NameIdx>) = 94, // Placeholder
|
||||
MapAdd {
|
||||
i: Arg<u32>,
|
||||
} = 95,
|
||||
MatchClass(Arg<u32>) = 96,
|
||||
PopJumpIfFalse {
|
||||
target: Arg<Label>,
|
||||
} = 97,
|
||||
PopJumpIfNone {
|
||||
target: Arg<Label>,
|
||||
} = 98, // Placeholder
|
||||
PopJumpIfNotNone {
|
||||
target: Arg<Label>,
|
||||
} = 99, // Placeholder
|
||||
PopJumpIfTrue {
|
||||
target: Arg<Label>,
|
||||
} = 100,
|
||||
RaiseVarargs {
|
||||
kind: Arg<RaiseKind>,
|
||||
} = 101,
|
||||
Reraise {
|
||||
depth: Arg<u32>,
|
||||
} = 102,
|
||||
ReturnConst {
|
||||
idx: Arg<u32>,
|
||||
} = 103,
|
||||
Send {
|
||||
target: Arg<Label>,
|
||||
} = 104,
|
||||
SetAdd {
|
||||
i: Arg<u32>,
|
||||
} = 105,
|
||||
SetFunctionAttribute {
|
||||
attr: Arg<MakeFunctionFlags>,
|
||||
} = 106,
|
||||
SetUpdate {
|
||||
i: Arg<u32>,
|
||||
} = 107, // Placeholder
|
||||
StoreAttr {
|
||||
idx: Arg<NameIdx>,
|
||||
} = 108,
|
||||
StoreDeref(Arg<NameIdx>) = 109,
|
||||
StoreFast(Arg<NameIdx>) = 110,
|
||||
StoreFastLoadFast {
|
||||
store_idx: Arg<NameIdx>,
|
||||
load_idx: Arg<NameIdx>,
|
||||
} = 111,
|
||||
StoreFastStoreFast {
|
||||
arg: Arg<u32>,
|
||||
} = 112, // Placeholder
|
||||
StoreGlobal(Arg<NameIdx>) = 113,
|
||||
StoreName(Arg<NameIdx>) = 114,
|
||||
Swap {
|
||||
index: Arg<u32>,
|
||||
} = 115,
|
||||
UnpackEx {
|
||||
args: Arg<UnpackExArgs>,
|
||||
} = 116,
|
||||
UnpackSequence {
|
||||
size: Arg<u32>,
|
||||
} = 117,
|
||||
YieldValue {
|
||||
arg: Arg<u32>,
|
||||
} = 118,
|
||||
Resume {
|
||||
arg: Arg<u32>,
|
||||
} = 149,
|
||||
// ==================== 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.
|
||||
Break {
|
||||
target: Arg<Label>,
|
||||
} = 119,
|
||||
BuildListFromTuples {
|
||||
size: Arg<u32>,
|
||||
} = 120,
|
||||
BuildMapForCall {
|
||||
size: Arg<u32>,
|
||||
} = 121,
|
||||
BuildSetFromTuples {
|
||||
size: Arg<u32>,
|
||||
} = 122,
|
||||
BuildTupleFromIter = 123,
|
||||
BuildTupleFromTuples {
|
||||
size: Arg<u32>,
|
||||
} = 124,
|
||||
Continue {
|
||||
target: Arg<Label>,
|
||||
} = 128,
|
||||
JumpIfFalseOrPop {
|
||||
target: Arg<Label>,
|
||||
} = 129,
|
||||
JumpIfTrueOrPop {
|
||||
target: Arg<Label>,
|
||||
} = 130,
|
||||
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
|
||||
}
|
||||
|
||||
const _: () = assert!(mem::size_of::<Instruction>() == 1);
|
||||
|
||||
impl From<Instruction> for u8 {
|
||||
#[inline]
|
||||
fn from(ins: Instruction) -> Self {
|
||||
// SAFETY: there's no padding bits
|
||||
unsafe { core::mem::transmute::<Instruction, Self>(ins) }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for Instruction {
|
||||
type Error = MarshalError;
|
||||
|
||||
#[inline]
|
||||
fn try_from(value: u8) -> Result<Self, MarshalError> {
|
||||
// CPython-compatible opcodes (0-118)
|
||||
let cpython_start = u8::from(Self::Cache);
|
||||
let cpython_end = u8::from(Self::YieldValue { arg: Arg::marker() });
|
||||
|
||||
// Resume has a non-contiguous opcode (149)
|
||||
let resume_id = u8::from(Self::Resume { arg: Arg::marker() });
|
||||
|
||||
// RustPython-only opcodes (explicit list to avoid gaps like 125-127)
|
||||
let custom_ops: &[u8] = &[
|
||||
u8::from(Self::Break {
|
||||
target: Arg::marker(),
|
||||
}),
|
||||
u8::from(Self::BuildListFromTuples {
|
||||
size: Arg::marker(),
|
||||
}),
|
||||
u8::from(Self::BuildMapForCall {
|
||||
size: Arg::marker(),
|
||||
}),
|
||||
u8::from(Self::BuildSetFromTuples {
|
||||
size: Arg::marker(),
|
||||
}),
|
||||
u8::from(Self::BuildTupleFromIter),
|
||||
u8::from(Self::BuildTupleFromTuples {
|
||||
size: Arg::marker(),
|
||||
}),
|
||||
// 125, 126, 127 are unused
|
||||
u8::from(Self::Continue {
|
||||
target: Arg::marker(),
|
||||
}),
|
||||
u8::from(Self::JumpIfFalseOrPop {
|
||||
target: Arg::marker(),
|
||||
}),
|
||||
u8::from(Self::JumpIfTrueOrPop {
|
||||
target: Arg::marker(),
|
||||
}),
|
||||
u8::from(Self::JumpIfNotExcMatch(Arg::marker())),
|
||||
u8::from(Self::SetExcInfo),
|
||||
u8::from(Self::Subscript),
|
||||
];
|
||||
|
||||
// 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
|
||||
|| custom_ops.contains(&value)
|
||||
|| (pseudo_start..=pseudo_end).contains(&value)
|
||||
{
|
||||
Ok(unsafe { core::mem::transmute::<u8, Self>(value) })
|
||||
} else {
|
||||
Err(Self::Error::InvalidBytecode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct CodeUnit {
|
||||
@@ -1085,8 +741,6 @@ impl Deref for CodeUnits {
|
||||
}
|
||||
}
|
||||
|
||||
use self::Instruction::*;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct MakeFunctionFlags: u8 {
|
||||
@@ -1727,447 +1381,6 @@ impl<C: Constant> fmt::Display for CodeObject<C> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Instruction {
|
||||
/// Gets the label stored inside this instruction, if it exists
|
||||
#[inline]
|
||||
pub const fn label_arg(&self) -> Option<Arg<Label>> {
|
||||
match self {
|
||||
Jump { target: l }
|
||||
| JumpBackward { target: l }
|
||||
| JumpBackwardNoInterrupt { target: l }
|
||||
| JumpForward { target: l }
|
||||
| JumpIfNotExcMatch(l)
|
||||
| PopJumpIfTrue { target: l }
|
||||
| PopJumpIfFalse { target: l }
|
||||
| JumpIfTrueOrPop { target: l }
|
||||
| JumpIfFalseOrPop { target: l }
|
||||
| ForIter { target: l }
|
||||
| Break { target: l }
|
||||
| Continue { target: l }
|
||||
| Send { target: l } => Some(*l),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
matches!(
|
||||
self,
|
||||
Jump { .. }
|
||||
| JumpForward { .. }
|
||||
| JumpBackward { .. }
|
||||
| JumpBackwardNoInterrupt { .. }
|
||||
| Continue { .. }
|
||||
| Break { .. }
|
||||
| ReturnValue
|
||||
| ReturnConst { .. }
|
||||
| RaiseVarargs { .. }
|
||||
| Reraise { .. }
|
||||
)
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
match self {
|
||||
Nop => 0,
|
||||
ImportName { .. } => -1,
|
||||
ImportFrom { .. } => 1,
|
||||
LoadFast(_) | LoadFastAndClear(_) | LoadName(_) | LoadGlobal(_) | LoadDeref(_) => 1,
|
||||
StoreFast(_) | StoreName(_) | StoreGlobal(_) | StoreDeref(_) => -1,
|
||||
StoreFastLoadFast { .. } => 0, // pop 1, push 1
|
||||
DeleteFast(_) | DeleteName(_) | DeleteGlobal(_) | DeleteDeref(_) => 0,
|
||||
LoadClosure(_) => 1,
|
||||
Subscript => -1,
|
||||
StoreSubscr => -3,
|
||||
LoadFromDictOrDeref(_) => 1,
|
||||
DeleteSubscr => -2,
|
||||
LoadAttr { .. } => 0,
|
||||
// LoadAttrMethod: pop obj, push method + self_or_null
|
||||
LoadAttrMethod { .. } => 1,
|
||||
StoreAttr { .. } => -2,
|
||||
DeleteAttr { .. } => -1,
|
||||
LoadConst { .. } => 1,
|
||||
Reserved => 0,
|
||||
BinaryOp { .. } | CompareOp { .. } => -1,
|
||||
BinarySubscr => -1,
|
||||
CopyItem { .. } => 1,
|
||||
PopTop => -1,
|
||||
Swap { .. } => 0,
|
||||
ToBool => 0,
|
||||
GetIter => 0,
|
||||
GetLen => 1,
|
||||
CallIntrinsic1 { .. } => 0, // Takes 1, pushes 1
|
||||
CallIntrinsic2 { .. } => -1, // Takes 2, pushes 1
|
||||
Continue { .. } => 0,
|
||||
Break { .. } => 0,
|
||||
Jump { .. } => 0,
|
||||
PopJumpIfTrue { .. } | PopJumpIfFalse { .. } => -1,
|
||||
JumpIfTrueOrPop { .. } | JumpIfFalseOrPop { .. } => {
|
||||
if jump {
|
||||
0
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
}
|
||||
MakeFunction => {
|
||||
// CPython 3.13 style: MakeFunction only pops code object
|
||||
-1 + 1 // pop code, push function
|
||||
}
|
||||
SetFunctionAttribute { .. } => {
|
||||
// pops attribute value and function, pushes function back
|
||||
-2 + 1
|
||||
}
|
||||
// Call: pops nargs + self_or_null + callable, pushes result
|
||||
Call { nargs } => -(nargs.get(arg) as i32) - 2 + 1,
|
||||
// CallKw: pops kw_names_tuple + nargs + self_or_null + callable, pushes result
|
||||
CallKw { nargs } => -1 - (nargs.get(arg) as i32) - 2 + 1,
|
||||
// CallFunctionEx: pops kwargs(if any) + args_tuple + self_or_null + callable, pushes result
|
||||
CallFunctionEx { has_kwargs } => -1 - (has_kwargs.get(arg) as i32) - 2 + 1,
|
||||
CheckEgMatch => 0, // pops 2 (exc, type), pushes 2 (rest, match)
|
||||
ConvertValue { .. } => 0,
|
||||
FormatSimple => 0,
|
||||
FormatWithSpec => -1,
|
||||
ForIter { .. } => {
|
||||
if jump {
|
||||
-1
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
IsOp(_) | ContainsOp(_) => -1,
|
||||
JumpIfNotExcMatch(_) => -2,
|
||||
ReturnValue => -1,
|
||||
ReturnConst { .. } => 0,
|
||||
Resume { .. } => 0,
|
||||
YieldValue { .. } => 0,
|
||||
// SEND: (receiver, val) -> (receiver, retval) - no change, both paths keep same depth
|
||||
Send { .. } => 0,
|
||||
// END_SEND: (receiver, value) -> (value)
|
||||
EndSend => -1,
|
||||
// CLEANUP_THROW: (sub_iter, last_sent_val, exc) -> (None, value) = 3 pop, 2 push = -1
|
||||
CleanupThrow => -1,
|
||||
SetExcInfo => 0,
|
||||
PushExcInfo => 1, // [exc] -> [prev_exc, exc]
|
||||
CheckExcMatch => 0, // [exc, type] -> [exc, bool] (pops type, pushes bool)
|
||||
Reraise { .. } => 0, // Exception raised, stack effect doesn't matter
|
||||
SetupAnnotations => 0,
|
||||
BeforeWith => 1, // push __exit__, then replace ctx_mgr with __enter__ result
|
||||
WithExceptStart => 1, // push __exit__ result
|
||||
PopBlock => 0,
|
||||
RaiseVarargs { kind } => {
|
||||
// Stack effects for different raise kinds:
|
||||
// - Reraise (0): gets from VM state, no stack pop
|
||||
// - Raise (1): pops 1 exception
|
||||
// - RaiseCause (2): pops 2 (exception + cause)
|
||||
// - ReraiseFromStack (3): pops 1 exception from stack
|
||||
match kind.get(arg) {
|
||||
RaiseKind::BareRaise => 0,
|
||||
RaiseKind::Raise => -1,
|
||||
RaiseKind::RaiseCause => -2,
|
||||
RaiseKind::ReraiseFromStack => -1,
|
||||
}
|
||||
}
|
||||
BuildString { size }
|
||||
| BuildTuple { size, .. }
|
||||
| BuildTupleFromTuples { size, .. }
|
||||
| BuildList { size, .. }
|
||||
| BuildListFromTuples { size, .. }
|
||||
| BuildSet { size, .. }
|
||||
| BuildSetFromTuples { size, .. } => -(size.get(arg) as i32) + 1,
|
||||
BuildTupleFromIter => 0,
|
||||
BuildMap { size } => {
|
||||
let nargs = size.get(arg) * 2;
|
||||
-(nargs as i32) + 1
|
||||
}
|
||||
BuildMapForCall { size } => {
|
||||
let nargs = size.get(arg);
|
||||
-(nargs as i32) + 1
|
||||
}
|
||||
DictUpdate { .. } => -1,
|
||||
BuildSlice { argc } => {
|
||||
// push 1
|
||||
// pops either 2/3
|
||||
// Default to Two (2 args) if arg is invalid
|
||||
1 - (argc
|
||||
.try_get(arg)
|
||||
.unwrap_or(BuildSliceArgCount::Two)
|
||||
.argc()
|
||||
.get() as i32)
|
||||
}
|
||||
ListAppend { .. } | SetAdd { .. } => -1,
|
||||
MapAdd { .. } => -2,
|
||||
LoadBuildClass => 1,
|
||||
UnpackSequence { size } => -1 + size.get(arg) as i32,
|
||||
UnpackEx { args } => {
|
||||
let UnpackExArgs { before, after } = args.get(arg);
|
||||
-1 + before as i32 + 1 + after as i32
|
||||
}
|
||||
PopExcept => 0,
|
||||
GetAwaitable => 0,
|
||||
BeforeAsyncWith => 1,
|
||||
GetAIter => 0,
|
||||
GetANext => 1,
|
||||
EndAsyncFor => -2, // pops (awaitable, exc) from stack
|
||||
MatchMapping | MatchSequence => 1, // Push bool result
|
||||
MatchKeys => 1, // Pop 2 (subject, keys), push 3 (subject, keys_or_none, values_or_none)
|
||||
MatchClass(_) => -2,
|
||||
ExtendedArg => 0,
|
||||
UnaryInvert => 0,
|
||||
UnaryNegative => 0,
|
||||
UnaryNot => 0,
|
||||
GetYieldFromIter => 0,
|
||||
PushNull => 1, // Push NULL for call protocol
|
||||
Cache => 0,
|
||||
BinarySlice => 0,
|
||||
BinaryOpInplaceAddUnicode => 0,
|
||||
EndFor => 0,
|
||||
ExitInitCheck => 0,
|
||||
InterpreterExit => 0,
|
||||
LoadAssertionError => 0,
|
||||
LoadLocals => 0,
|
||||
ReturnGenerator => 0,
|
||||
StoreSlice => 0,
|
||||
DictMerge { .. } => 0,
|
||||
BuildConstKeyMap { .. } => 0,
|
||||
CopyFreeVars { .. } => 0,
|
||||
EnterExecutor => 0,
|
||||
JumpBackwardNoInterrupt { .. } => 0,
|
||||
JumpBackward { .. } => 0,
|
||||
JumpForward { .. } => 0,
|
||||
ListExtend { .. } => 0,
|
||||
LoadFastCheck(_) => 0,
|
||||
LoadFastLoadFast { .. } => 0,
|
||||
LoadFromDictOrGlobals(_) => 0,
|
||||
SetUpdate { .. } => 0,
|
||||
MakeCell(_) => 0,
|
||||
LoadSuperAttr { .. } => 0,
|
||||
StoreFastStoreFast { .. } => 0,
|
||||
PopJumpIfNone { .. } => 0,
|
||||
PopJumpIfNotNone { .. } => 0,
|
||||
}
|
||||
}
|
||||
|
||||
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)]
|
||||
fn fmt_dis(
|
||||
&self,
|
||||
arg: OpArg,
|
||||
f: &mut fmt::Formatter<'_>,
|
||||
ctx: &impl InstrDisplayContext,
|
||||
expand_code_objects: bool,
|
||||
pad: usize,
|
||||
level: usize,
|
||||
) -> fmt::Result {
|
||||
macro_rules! w {
|
||||
($variant:ident) => {
|
||||
write!(f, stringify!($variant))
|
||||
};
|
||||
($variant:ident, $map:ident = $arg_marker:expr) => {{
|
||||
let arg = $arg_marker.get(arg);
|
||||
write!(f, "{:pad$}({}, {})", stringify!($variant), arg, $map(arg))
|
||||
}};
|
||||
($variant:ident, $arg_marker:expr) => {
|
||||
write!(f, "{:pad$}({})", stringify!($variant), $arg_marker.get(arg))
|
||||
};
|
||||
($variant:ident, ?$arg_marker:expr) => {
|
||||
write!(
|
||||
f,
|
||||
"{:pad$}({:?})",
|
||||
stringify!($variant),
|
||||
$arg_marker.get(arg)
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
let varname = |i: u32| ctx.get_varname(i as usize);
|
||||
let name = |i: u32| ctx.get_name(i as usize);
|
||||
let cell_name = |i: u32| ctx.get_cell_name(i as usize);
|
||||
|
||||
let fmt_const =
|
||||
|op: &str, arg: OpArg, f: &mut fmt::Formatter<'_>, idx: &Arg<u32>| -> fmt::Result {
|
||||
let value = ctx.get_constant(idx.get(arg) as usize);
|
||||
match value.borrow_constant() {
|
||||
BorrowedConstant::Code { code } if expand_code_objects => {
|
||||
write!(f, "{op:pad$}({code:?}):")?;
|
||||
code.display_inner(f, true, level + 1)?;
|
||||
Ok(())
|
||||
}
|
||||
c => {
|
||||
write!(f, "{op:pad$}(")?;
|
||||
c.fmt_display(f)?;
|
||||
write!(f, ")")
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match self {
|
||||
BeforeAsyncWith => w!(BEFORE_ASYNC_WITH),
|
||||
BeforeWith => w!(BEFORE_WITH),
|
||||
BinaryOp { op } => write!(f, "{:pad$}({})", "BINARY_OP", op.get(arg)),
|
||||
BinarySubscr => w!(BINARY_SUBSCR),
|
||||
Break { target } => w!(BREAK, target),
|
||||
BuildList { size } => w!(BUILD_LIST, size),
|
||||
BuildListFromTuples { size } => w!(BUILD_LIST_FROM_TUPLES, size),
|
||||
BuildMap { size } => w!(BUILD_MAP, size),
|
||||
BuildMapForCall { size } => w!(BUILD_MAP_FOR_CALL, size),
|
||||
BuildSet { size } => w!(BUILD_SET, size),
|
||||
BuildSetFromTuples { size } => w!(BUILD_SET_FROM_TUPLES, size),
|
||||
BuildSlice { argc } => w!(BUILD_SLICE, ?argc),
|
||||
BuildString { size } => w!(BUILD_STRING, size),
|
||||
BuildTuple { size } => w!(BUILD_TUPLE, size),
|
||||
BuildTupleFromIter => w!(BUILD_TUPLE_FROM_ITER),
|
||||
BuildTupleFromTuples { size } => w!(BUILD_TUPLE_FROM_TUPLES, size),
|
||||
Call { nargs } => w!(CALL, nargs),
|
||||
CallFunctionEx { has_kwargs } => w!(CALL_FUNCTION_EX, has_kwargs),
|
||||
CallKw { nargs } => w!(CALL_KW, nargs),
|
||||
CallIntrinsic1 { func } => w!(CALL_INTRINSIC_1, ?func),
|
||||
CallIntrinsic2 { func } => w!(CALL_INTRINSIC_2, ?func),
|
||||
CheckEgMatch => w!(CHECK_EG_MATCH),
|
||||
CheckExcMatch => w!(CHECK_EXC_MATCH),
|
||||
CleanupThrow => w!(CLEANUP_THROW),
|
||||
CompareOp { op } => w!(COMPARE_OP, ?op),
|
||||
ContainsOp(inv) => w!(CONTAINS_OP, ?inv),
|
||||
Continue { target } => w!(CONTINUE, target),
|
||||
ConvertValue { oparg } => write!(f, "{:pad$}{}", "CONVERT_VALUE", oparg.get(arg)),
|
||||
CopyItem { index } => w!(COPY, index),
|
||||
DeleteAttr { idx } => w!(DELETE_ATTR, name = idx),
|
||||
DeleteDeref(idx) => w!(DELETE_DEREF, cell_name = idx),
|
||||
DeleteFast(idx) => w!(DELETE_FAST, varname = idx),
|
||||
DeleteGlobal(idx) => w!(DELETE_GLOBAL, name = idx),
|
||||
DeleteName(idx) => w!(DELETE_NAME, name = idx),
|
||||
DeleteSubscr => w!(DELETE_SUBSCR),
|
||||
DictUpdate { index } => w!(DICT_UPDATE, index),
|
||||
EndAsyncFor => w!(END_ASYNC_FOR),
|
||||
EndSend => w!(END_SEND),
|
||||
ExtendedArg => w!(EXTENDED_ARG, Arg::<u32>::marker()),
|
||||
ForIter { target } => w!(FOR_ITER, target),
|
||||
FormatSimple => w!(FORMAT_SIMPLE),
|
||||
FormatWithSpec => w!(FORMAT_WITH_SPEC),
|
||||
GetAIter => w!(GET_AITER),
|
||||
GetANext => w!(GET_ANEXT),
|
||||
GetAwaitable => w!(GET_AWAITABLE),
|
||||
Reserved => w!(RESERVED),
|
||||
GetIter => w!(GET_ITER),
|
||||
GetLen => w!(GET_LEN),
|
||||
ImportFrom { idx } => w!(IMPORT_FROM, name = idx),
|
||||
ImportName { idx } => w!(IMPORT_NAME, name = idx),
|
||||
IsOp(inv) => w!(IS_OP, ?inv),
|
||||
Jump { target } => w!(JUMP, target),
|
||||
JumpBackward { target } => w!(JUMP_BACKWARD, target),
|
||||
JumpBackwardNoInterrupt { target } => w!(JUMP_BACKWARD_NO_INTERRUPT, target),
|
||||
JumpForward { target } => w!(JUMP_FORWARD, target),
|
||||
JumpIfFalseOrPop { target } => w!(JUMP_IF_FALSE_OR_POP, target),
|
||||
JumpIfNotExcMatch(target) => w!(JUMP_IF_NOT_EXC_MATCH, target),
|
||||
JumpIfTrueOrPop { target } => w!(JUMP_IF_TRUE_OR_POP, target),
|
||||
ListAppend { i } => w!(LIST_APPEND, i),
|
||||
LoadAttr { idx } => {
|
||||
let encoded = idx.get(arg);
|
||||
let (name_idx, is_method) = decode_load_attr_arg(encoded);
|
||||
let attr_name = name(name_idx);
|
||||
if is_method {
|
||||
write!(
|
||||
f,
|
||||
"{:pad$}({}, {}, method=true)",
|
||||
"LOAD_ATTR", encoded, attr_name
|
||||
)
|
||||
} else {
|
||||
write!(f, "{:pad$}({}, {})", "LOAD_ATTR", encoded, attr_name)
|
||||
}
|
||||
}
|
||||
LoadAttrMethod { idx } => w!(LOAD_ATTR_METHOD, name = idx),
|
||||
LoadBuildClass => w!(LOAD_BUILD_CLASS),
|
||||
LoadFromDictOrDeref(i) => w!(LOAD_FROM_DICT_OR_DEREF, cell_name = i),
|
||||
LoadClosure(i) => w!(LOAD_CLOSURE, cell_name = i),
|
||||
LoadConst { idx } => fmt_const("LOAD_CONST", arg, f, idx),
|
||||
LoadDeref(idx) => w!(LOAD_DEREF, cell_name = idx),
|
||||
LoadFast(idx) => w!(LOAD_FAST, varname = idx),
|
||||
LoadFastAndClear(idx) => w!(LOAD_FAST_AND_CLEAR, varname = idx),
|
||||
LoadGlobal(idx) => w!(LOAD_GLOBAL, name = idx),
|
||||
LoadName(idx) => w!(LOAD_NAME, name = idx),
|
||||
MakeFunction => w!(MAKE_FUNCTION),
|
||||
MapAdd { i } => w!(MAP_ADD, i),
|
||||
MatchClass(arg) => w!(MATCH_CLASS, arg),
|
||||
MatchKeys => w!(MATCH_KEYS),
|
||||
MatchMapping => w!(MATCH_MAPPING),
|
||||
MatchSequence => w!(MATCH_SEQUENCE),
|
||||
Nop => w!(NOP),
|
||||
PopBlock => w!(POP_BLOCK),
|
||||
PopExcept => w!(POP_EXCEPT),
|
||||
PopJumpIfFalse { target } => w!(POP_JUMP_IF_FALSE, target),
|
||||
PopJumpIfTrue { target } => w!(POP_JUMP_IF_TRUE, target),
|
||||
PopTop => w!(POP_TOP),
|
||||
PushExcInfo => w!(PUSH_EXC_INFO),
|
||||
PushNull => w!(PUSH_NULL),
|
||||
RaiseVarargs { kind } => w!(RAISE_VARARGS, ?kind),
|
||||
Reraise { depth } => w!(RERAISE, depth),
|
||||
Resume { arg } => w!(RESUME, arg),
|
||||
ReturnConst { idx } => fmt_const("RETURN_CONST", arg, f, idx),
|
||||
ReturnValue => w!(RETURN_VALUE),
|
||||
Send { target } => w!(SEND, target),
|
||||
SetAdd { i } => w!(SET_ADD, i),
|
||||
SetExcInfo => w!(SET_EXC_INFO),
|
||||
SetFunctionAttribute { attr } => w!(SET_FUNCTION_ATTRIBUTE, ?attr),
|
||||
SetupAnnotations => w!(SETUP_ANNOTATIONS),
|
||||
StoreAttr { idx } => w!(STORE_ATTR, name = idx),
|
||||
StoreDeref(idx) => w!(STORE_DEREF, cell_name = idx),
|
||||
StoreFast(idx) => w!(STORE_FAST, varname = idx),
|
||||
StoreFastLoadFast {
|
||||
store_idx,
|
||||
load_idx,
|
||||
} => {
|
||||
write!(f, "STORE_FAST_LOAD_FAST")?;
|
||||
write!(f, " ({}, {})", store_idx.get(arg), load_idx.get(arg))
|
||||
}
|
||||
StoreGlobal(idx) => w!(STORE_GLOBAL, name = idx),
|
||||
StoreName(idx) => w!(STORE_NAME, name = idx),
|
||||
StoreSubscr => w!(STORE_SUBSCR),
|
||||
Subscript => w!(SUBSCRIPT),
|
||||
Swap { index } => w!(SWAP, index),
|
||||
ToBool => w!(TO_BOOL),
|
||||
UnpackEx { args } => w!(UNPACK_EX, args),
|
||||
UnpackSequence { size } => w!(UNPACK_SEQUENCE, size),
|
||||
WithExceptStart => w!(WITH_EXCEPT_START),
|
||||
UnaryInvert => w!(UNARY_INVERT),
|
||||
UnaryNegative => w!(UNARY_NEGATIVE),
|
||||
UnaryNot => w!(UNARY_NOT),
|
||||
YieldValue { arg } => w!(YIELD_VALUE, arg),
|
||||
GetYieldFromIter => w!(GET_YIELD_FROM_ITER),
|
||||
_ => w!(RUSTPYTHON_PLACEHOLDER),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait InstrDisplayContext {
|
||||
type Constant: Constant;
|
||||
|
||||
|
||||
@@ -0,0 +1,822 @@
|
||||
use alloc::fmt;
|
||||
use std::mem;
|
||||
|
||||
use crate::{
|
||||
bytecode::{
|
||||
Arg, BinaryOperator, BorrowedConstant, BuildSliceArgCount, ComparisonOperator, Constant,
|
||||
ConvertValueOparg, InstrDisplayContext, IntrinsicFunction1, IntrinsicFunction2, Invert,
|
||||
Label, MakeFunctionFlags, NameIdx, OpArg, RaiseKind, UnpackExArgs, decode_load_attr_arg,
|
||||
},
|
||||
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)]
|
||||
#[repr(u8)]
|
||||
pub enum Instruction {
|
||||
// ==================== No-argument instructions (opcode < 44) ====================
|
||||
Cache = 0, // Placeholder
|
||||
BeforeAsyncWith = 1,
|
||||
BeforeWith = 2,
|
||||
BinaryOpInplaceAddUnicode = 3, // Placeholder
|
||||
BinarySlice = 4, // Placeholder
|
||||
BinarySubscr = 5,
|
||||
CheckEgMatch = 6,
|
||||
CheckExcMatch = 7,
|
||||
CleanupThrow = 8,
|
||||
DeleteSubscr = 9,
|
||||
EndAsyncFor = 10,
|
||||
EndFor = 11, // Placeholder
|
||||
EndSend = 12,
|
||||
ExitInitCheck = 13, // Placeholder
|
||||
FormatSimple = 14,
|
||||
FormatWithSpec = 15,
|
||||
GetAIter = 16,
|
||||
Reserved = 17,
|
||||
GetANext = 18,
|
||||
GetIter = 19,
|
||||
GetLen = 20,
|
||||
GetYieldFromIter = 21,
|
||||
InterpreterExit = 22, // Placeholder
|
||||
LoadAssertionError = 23, // Placeholder
|
||||
LoadBuildClass = 24,
|
||||
LoadLocals = 25, // Placeholder
|
||||
MakeFunction = 26,
|
||||
MatchKeys = 27,
|
||||
MatchMapping = 28,
|
||||
MatchSequence = 29,
|
||||
Nop = 30,
|
||||
PopExcept = 31,
|
||||
PopTop = 32,
|
||||
PushExcInfo = 33,
|
||||
PushNull = 34, // Placeholder
|
||||
ReturnGenerator = 35, // Placeholder
|
||||
ReturnValue = 36,
|
||||
SetupAnnotations = 37,
|
||||
StoreSlice = 38, // Placeholder
|
||||
StoreSubscr = 39,
|
||||
ToBool = 40,
|
||||
UnaryInvert = 41,
|
||||
UnaryNegative = 42,
|
||||
UnaryNot = 43,
|
||||
WithExceptStart = 44,
|
||||
// ==================== With-argument instructions (opcode > 44) ====================
|
||||
BinaryOp {
|
||||
op: Arg<BinaryOperator>,
|
||||
} = 45,
|
||||
BuildConstKeyMap {
|
||||
size: Arg<u32>,
|
||||
} = 46, // Placeholder
|
||||
BuildList {
|
||||
size: Arg<u32>,
|
||||
} = 47,
|
||||
BuildMap {
|
||||
size: Arg<u32>,
|
||||
} = 48,
|
||||
BuildSet {
|
||||
size: Arg<u32>,
|
||||
} = 49,
|
||||
BuildSlice {
|
||||
argc: Arg<BuildSliceArgCount>,
|
||||
} = 50,
|
||||
BuildString {
|
||||
size: Arg<u32>,
|
||||
} = 51,
|
||||
BuildTuple {
|
||||
size: Arg<u32>,
|
||||
} = 52,
|
||||
Call {
|
||||
nargs: Arg<u32>,
|
||||
} = 53,
|
||||
CallFunctionEx {
|
||||
has_kwargs: Arg<bool>,
|
||||
} = 54,
|
||||
CallIntrinsic1 {
|
||||
func: Arg<IntrinsicFunction1>,
|
||||
} = 55,
|
||||
CallIntrinsic2 {
|
||||
func: Arg<IntrinsicFunction2>,
|
||||
} = 56,
|
||||
CallKw {
|
||||
nargs: Arg<u32>,
|
||||
} = 57,
|
||||
CompareOp {
|
||||
op: Arg<ComparisonOperator>,
|
||||
} = 58,
|
||||
ContainsOp(Arg<Invert>) = 59,
|
||||
ConvertValue {
|
||||
oparg: Arg<ConvertValueOparg>,
|
||||
} = 60,
|
||||
Copy {
|
||||
index: Arg<u32>,
|
||||
} = 61,
|
||||
CopyFreeVars {
|
||||
count: Arg<u32>,
|
||||
} = 62, // Placeholder
|
||||
DeleteAttr {
|
||||
idx: Arg<NameIdx>,
|
||||
} = 63,
|
||||
DeleteDeref(Arg<NameIdx>) = 64,
|
||||
DeleteFast(Arg<NameIdx>) = 65,
|
||||
DeleteGlobal(Arg<NameIdx>) = 66,
|
||||
DeleteName(Arg<NameIdx>) = 67,
|
||||
DictMerge {
|
||||
index: Arg<u32>,
|
||||
} = 68, // Placeholder
|
||||
DictUpdate {
|
||||
index: Arg<u32>,
|
||||
} = 69,
|
||||
EnterExecutor = 70, // Placeholder
|
||||
ExtendedArg = 71,
|
||||
ForIter {
|
||||
target: Arg<Label>,
|
||||
} = 72,
|
||||
GetAwaitable = 73, // TODO: Make this instruction to hold an oparg
|
||||
ImportFrom {
|
||||
idx: Arg<NameIdx>,
|
||||
} = 74,
|
||||
ImportName {
|
||||
idx: Arg<NameIdx>,
|
||||
} = 75,
|
||||
IsOp(Arg<Invert>) = 76,
|
||||
JumpBackward {
|
||||
target: Arg<Label>,
|
||||
} = 77, // Placeholder
|
||||
JumpBackwardNoInterrupt {
|
||||
target: Arg<Label>,
|
||||
} = 78, // Placeholder
|
||||
JumpForward {
|
||||
target: Arg<Label>,
|
||||
} = 79, // Placeholder
|
||||
ListAppend {
|
||||
i: Arg<u32>,
|
||||
} = 80,
|
||||
ListExtend {
|
||||
i: Arg<u32>,
|
||||
} = 81, // Placeholder
|
||||
LoadAttr {
|
||||
idx: Arg<NameIdx>,
|
||||
} = 82,
|
||||
LoadConst {
|
||||
idx: Arg<u32>,
|
||||
} = 83,
|
||||
LoadDeref(Arg<NameIdx>) = 84,
|
||||
LoadFast(Arg<NameIdx>) = 85,
|
||||
LoadFastAndClear(Arg<NameIdx>) = 86,
|
||||
LoadFastCheck(Arg<NameIdx>) = 87, // Placeholder
|
||||
LoadFastLoadFast {
|
||||
arg: Arg<u32>,
|
||||
} = 88, // Placeholder
|
||||
LoadFromDictOrDeref(Arg<NameIdx>) = 89,
|
||||
LoadFromDictOrGlobals(Arg<NameIdx>) = 90, // Placeholder
|
||||
LoadGlobal(Arg<NameIdx>) = 91,
|
||||
LoadName(Arg<NameIdx>) = 92,
|
||||
LoadSuperAttr {
|
||||
arg: Arg<u32>,
|
||||
} = 93, // Placeholder
|
||||
MakeCell(Arg<NameIdx>) = 94, // Placeholder
|
||||
MapAdd {
|
||||
i: Arg<u32>,
|
||||
} = 95,
|
||||
MatchClass(Arg<u32>) = 96,
|
||||
PopJumpIfFalse {
|
||||
target: Arg<Label>,
|
||||
} = 97,
|
||||
PopJumpIfNone {
|
||||
target: Arg<Label>,
|
||||
} = 98, // Placeholder
|
||||
PopJumpIfNotNone {
|
||||
target: Arg<Label>,
|
||||
} = 99, // Placeholder
|
||||
PopJumpIfTrue {
|
||||
target: Arg<Label>,
|
||||
} = 100,
|
||||
RaiseVarargs {
|
||||
kind: Arg<RaiseKind>,
|
||||
} = 101,
|
||||
Reraise {
|
||||
depth: Arg<u32>,
|
||||
} = 102,
|
||||
ReturnConst {
|
||||
idx: Arg<u32>,
|
||||
} = 103,
|
||||
Send {
|
||||
target: Arg<Label>,
|
||||
} = 104,
|
||||
SetAdd {
|
||||
i: Arg<u32>,
|
||||
} = 105,
|
||||
SetFunctionAttribute {
|
||||
attr: Arg<MakeFunctionFlags>,
|
||||
} = 106,
|
||||
SetUpdate {
|
||||
i: Arg<u32>,
|
||||
} = 107, // Placeholder
|
||||
StoreAttr {
|
||||
idx: Arg<NameIdx>,
|
||||
} = 108,
|
||||
StoreDeref(Arg<NameIdx>) = 109,
|
||||
StoreFast(Arg<NameIdx>) = 110,
|
||||
StoreFastLoadFast {
|
||||
store_idx: Arg<NameIdx>,
|
||||
load_idx: Arg<NameIdx>,
|
||||
} = 111,
|
||||
StoreFastStoreFast {
|
||||
arg: Arg<u32>,
|
||||
} = 112, // Placeholder
|
||||
StoreGlobal(Arg<NameIdx>) = 113,
|
||||
StoreName(Arg<NameIdx>) = 114,
|
||||
Swap {
|
||||
index: Arg<u32>,
|
||||
} = 115,
|
||||
UnpackEx {
|
||||
args: Arg<UnpackExArgs>,
|
||||
} = 116,
|
||||
UnpackSequence {
|
||||
size: Arg<u32>,
|
||||
} = 117,
|
||||
YieldValue {
|
||||
arg: Arg<u32>,
|
||||
} = 118,
|
||||
Resume {
|
||||
arg: Arg<u32>,
|
||||
} = 149,
|
||||
// ==================== 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.
|
||||
Break {
|
||||
target: Arg<Label>,
|
||||
} = 119,
|
||||
BuildListFromTuples {
|
||||
size: Arg<u32>,
|
||||
} = 120,
|
||||
BuildMapForCall {
|
||||
size: Arg<u32>,
|
||||
} = 121,
|
||||
BuildSetFromTuples {
|
||||
size: Arg<u32>,
|
||||
} = 122,
|
||||
BuildTupleFromIter = 123,
|
||||
BuildTupleFromTuples {
|
||||
size: Arg<u32>,
|
||||
} = 124,
|
||||
Continue {
|
||||
target: Arg<Label>,
|
||||
} = 128,
|
||||
JumpIfFalseOrPop {
|
||||
target: Arg<Label>,
|
||||
} = 129,
|
||||
JumpIfTrueOrPop {
|
||||
target: Arg<Label>,
|
||||
} = 130,
|
||||
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
|
||||
}
|
||||
|
||||
const _: () = assert!(mem::size_of::<Instruction>() == 1);
|
||||
|
||||
impl From<Instruction> for u8 {
|
||||
#[inline]
|
||||
fn from(ins: Instruction) -> Self {
|
||||
// SAFETY: there's no padding bits
|
||||
unsafe { core::mem::transmute::<Instruction, Self>(ins) }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for Instruction {
|
||||
type Error = MarshalError;
|
||||
|
||||
#[inline]
|
||||
fn try_from(value: u8) -> Result<Self, MarshalError> {
|
||||
// CPython-compatible opcodes (0-118)
|
||||
let cpython_start = u8::from(Self::Cache);
|
||||
let cpython_end = u8::from(Self::YieldValue { arg: Arg::marker() });
|
||||
|
||||
// Resume has a non-contiguous opcode (149)
|
||||
let resume_id = u8::from(Self::Resume { arg: Arg::marker() });
|
||||
|
||||
// RustPython-only opcodes (explicit list to avoid gaps like 125-127)
|
||||
let custom_ops: &[u8] = &[
|
||||
u8::from(Self::Break {
|
||||
target: Arg::marker(),
|
||||
}),
|
||||
u8::from(Self::BuildListFromTuples {
|
||||
size: Arg::marker(),
|
||||
}),
|
||||
u8::from(Self::BuildMapForCall {
|
||||
size: Arg::marker(),
|
||||
}),
|
||||
u8::from(Self::BuildSetFromTuples {
|
||||
size: Arg::marker(),
|
||||
}),
|
||||
u8::from(Self::BuildTupleFromIter),
|
||||
u8::from(Self::BuildTupleFromTuples {
|
||||
size: Arg::marker(),
|
||||
}),
|
||||
// 125, 126, 127 are unused
|
||||
u8::from(Self::Continue {
|
||||
target: Arg::marker(),
|
||||
}),
|
||||
u8::from(Self::JumpIfFalseOrPop {
|
||||
target: Arg::marker(),
|
||||
}),
|
||||
u8::from(Self::JumpIfTrueOrPop {
|
||||
target: Arg::marker(),
|
||||
}),
|
||||
u8::from(Self::JumpIfNotExcMatch(Arg::marker())),
|
||||
u8::from(Self::SetExcInfo),
|
||||
u8::from(Self::Subscript),
|
||||
];
|
||||
|
||||
// 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
|
||||
|| custom_ops.contains(&value)
|
||||
|| (pseudo_start..=pseudo_end).contains(&value)
|
||||
{
|
||||
Ok(unsafe { core::mem::transmute::<u8, Self>(value) })
|
||||
} else {
|
||||
Err(Self::Error::InvalidBytecode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Instruction {
|
||||
/// Gets the label stored inside this instruction, if it exists
|
||||
#[inline]
|
||||
pub const fn label_arg(&self) -> Option<Arg<Label>> {
|
||||
match self {
|
||||
Self::Jump { target: l }
|
||||
| Self::JumpBackward { target: l }
|
||||
| Self::JumpBackwardNoInterrupt { target: l }
|
||||
| Self::JumpForward { target: l }
|
||||
| Self::JumpIfNotExcMatch(l)
|
||||
| Self::PopJumpIfTrue { target: l }
|
||||
| Self::PopJumpIfFalse { target: l }
|
||||
| Self::JumpIfTrueOrPop { target: l }
|
||||
| Self::JumpIfFalseOrPop { target: l }
|
||||
| Self::ForIter { target: l }
|
||||
| Self::Break { target: l }
|
||||
| Self::Continue { target: l }
|
||||
| Self::Send { target: l } => Some(*l),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
matches!(
|
||||
self,
|
||||
Self::Jump { .. }
|
||||
| Self::JumpForward { .. }
|
||||
| Self::JumpBackward { .. }
|
||||
| Self::JumpBackwardNoInterrupt { .. }
|
||||
| Self::Continue { .. }
|
||||
| Self::Break { .. }
|
||||
| Self::ReturnValue
|
||||
| Self::ReturnConst { .. }
|
||||
| Self::RaiseVarargs { .. }
|
||||
| Self::Reraise { .. }
|
||||
)
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
match self {
|
||||
Self::Nop => 0,
|
||||
Self::ImportName { .. } => -1,
|
||||
Self::ImportFrom { .. } => 1,
|
||||
Self::LoadFast(_) => 1,
|
||||
Self::LoadFastAndClear(_) => 1,
|
||||
Self::LoadName(_) => 1,
|
||||
Self::LoadGlobal(_) => 1,
|
||||
Self::LoadDeref(_) => 1,
|
||||
Self::StoreFast(_) => -1,
|
||||
Self::StoreName(_) => -1,
|
||||
Self::StoreGlobal(_) => -1,
|
||||
Self::StoreDeref(_) => -1,
|
||||
Self::StoreFastLoadFast { .. } => 0, // pop 1, push 1
|
||||
Self::DeleteFast(_) => 0,
|
||||
Self::DeleteName(_) => 0,
|
||||
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,
|
||||
Self::Reserved => 0,
|
||||
Self::BinaryOp { .. } => -1,
|
||||
Self::CompareOp { .. } => -1,
|
||||
Self::BinarySubscr => -1,
|
||||
Self::Copy { .. } => 1,
|
||||
Self::PopTop => -1,
|
||||
Self::Swap { .. } => 0,
|
||||
Self::ToBool => 0,
|
||||
Self::GetIter => 0,
|
||||
Self::GetLen => 1,
|
||||
Self::CallIntrinsic1 { .. } => 0, // Takes 1, pushes 1
|
||||
Self::CallIntrinsic2 { .. } => -1, // Takes 2, pushes 1
|
||||
Self::Continue { .. } => 0,
|
||||
Self::Break { .. } => 0,
|
||||
Self::Jump { .. } => 0,
|
||||
Self::PopJumpIfTrue { .. } => -1,
|
||||
Self::PopJumpIfFalse { .. } => -1,
|
||||
Self::JumpIfTrueOrPop { .. } => {
|
||||
if jump {
|
||||
0
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
}
|
||||
Self::JumpIfFalseOrPop { .. } => {
|
||||
if jump {
|
||||
0
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
}
|
||||
Self::MakeFunction => {
|
||||
// CPython 3.13 style: MakeFunction only pops code object
|
||||
-1 + 1 // pop code, push function
|
||||
}
|
||||
Self::SetFunctionAttribute { .. } => {
|
||||
// pops attribute value and function, pushes function back
|
||||
-2 + 1
|
||||
}
|
||||
// Call: pops nargs + self_or_null + callable, pushes result
|
||||
Self::Call { nargs } => -(nargs.get(arg) as i32) - 2 + 1,
|
||||
// CallKw: pops kw_names_tuple + nargs + self_or_null + callable, pushes result
|
||||
Self::CallKw { nargs } => -1 - (nargs.get(arg) as i32) - 2 + 1,
|
||||
// CallFunctionEx: pops kwargs(if any) + args_tuple + self_or_null + callable, pushes result
|
||||
Self::CallFunctionEx { has_kwargs } => -1 - (has_kwargs.get(arg) as i32) - 2 + 1,
|
||||
Self::CheckEgMatch => 0, // pops 2 (exc, type), pushes 2 (rest, match)
|
||||
Self::ConvertValue { .. } => 0,
|
||||
Self::FormatSimple => 0,
|
||||
Self::FormatWithSpec => -1,
|
||||
Self::ForIter { .. } => {
|
||||
if jump {
|
||||
-1
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
Self::IsOp(_) => -1,
|
||||
Self::ContainsOp(_) => -1,
|
||||
Self::JumpIfNotExcMatch(_) => -2,
|
||||
Self::ReturnValue => -1,
|
||||
Self::ReturnConst { .. } => 0,
|
||||
Self::Resume { .. } => 0,
|
||||
Self::YieldValue { .. } => 0,
|
||||
// SEND: (receiver, val) -> (receiver, retval) - no change, both paths keep same depth
|
||||
Self::Send { .. } => 0,
|
||||
// END_SEND: (receiver, value) -> (value)
|
||||
Self::EndSend => -1,
|
||||
// CLEANUP_THROW: (sub_iter, last_sent_val, exc) -> (None, value) = 3 pop, 2 push = -1
|
||||
Self::CleanupThrow => -1,
|
||||
Self::SetExcInfo => 0,
|
||||
Self::PushExcInfo => 1, // [exc] -> [prev_exc, exc]
|
||||
Self::CheckExcMatch => 0, // [exc, type] -> [exc, bool] (pops type, pushes bool)
|
||||
Self::Reraise { .. } => 0, // Exception raised, stack effect doesn't matter
|
||||
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
|
||||
// - Raise (1): pops 1 exception
|
||||
// - RaiseCause (2): pops 2 (exception + cause)
|
||||
// - ReraiseFromStack (3): pops 1 exception from stack
|
||||
match kind.get(arg) {
|
||||
RaiseKind::BareRaise => 0,
|
||||
RaiseKind::Raise => -1,
|
||||
RaiseKind::RaiseCause => -2,
|
||||
RaiseKind::ReraiseFromStack => -1,
|
||||
}
|
||||
}
|
||||
Self::BuildString { size } => -(size.get(arg) as i32) + 1,
|
||||
Self::BuildTuple { size, .. } => -(size.get(arg) as i32) + 1,
|
||||
Self::BuildTupleFromTuples { size, .. } => -(size.get(arg) as i32) + 1,
|
||||
Self::BuildList { size, .. } => -(size.get(arg) as i32) + 1,
|
||||
Self::BuildListFromTuples { size, .. } => -(size.get(arg) as i32) + 1,
|
||||
Self::BuildSet { size, .. } => -(size.get(arg) as i32) + 1,
|
||||
Self::BuildSetFromTuples { size, .. } => -(size.get(arg) as i32) + 1,
|
||||
Self::BuildTupleFromIter => 0,
|
||||
Self::BuildMap { size } => {
|
||||
let nargs = size.get(arg) * 2;
|
||||
-(nargs as i32) + 1
|
||||
}
|
||||
Self::BuildMapForCall { size } => {
|
||||
let nargs = size.get(arg);
|
||||
-(nargs as i32) + 1
|
||||
}
|
||||
Self::DictUpdate { .. } => -1,
|
||||
Self::BuildSlice { argc } => {
|
||||
// push 1
|
||||
// pops either 2/3
|
||||
// Default to Two (2 args) if arg is invalid
|
||||
1 - (argc
|
||||
.try_get(arg)
|
||||
.unwrap_or(BuildSliceArgCount::Two)
|
||||
.argc()
|
||||
.get() as i32)
|
||||
}
|
||||
Self::ListAppend { .. } => -1,
|
||||
Self::SetAdd { .. } => -1,
|
||||
Self::MapAdd { .. } => -2,
|
||||
Self::LoadBuildClass => 1,
|
||||
Self::UnpackSequence { size } => -1 + size.get(arg) as i32,
|
||||
Self::UnpackEx { args } => {
|
||||
let UnpackExArgs { before, after } = args.get(arg);
|
||||
-1 + before as i32 + 1 + after as i32
|
||||
}
|
||||
Self::PopExcept => 0,
|
||||
Self::GetAwaitable => 0,
|
||||
Self::BeforeAsyncWith => 1,
|
||||
Self::GetAIter => 0,
|
||||
Self::GetANext => 1,
|
||||
Self::EndAsyncFor => -2, // pops (awaitable, exc) from stack
|
||||
Self::MatchMapping => 1, // Push bool result
|
||||
Self::MatchSequence => 1, // Push bool result
|
||||
Self::MatchKeys => 1, // Pop 2 (subject, keys), push 3 (subject, keys_or_none, values_or_none)
|
||||
Self::MatchClass(_) => -2,
|
||||
Self::ExtendedArg => 0,
|
||||
Self::UnaryInvert => 0,
|
||||
Self::UnaryNegative => 0,
|
||||
Self::UnaryNot => 0,
|
||||
Self::GetYieldFromIter => 0,
|
||||
Self::PushNull => 1, // Push NULL for call protocol
|
||||
Self::Cache => 0,
|
||||
Self::BinarySlice => 0,
|
||||
Self::BinaryOpInplaceAddUnicode => 0,
|
||||
Self::EndFor => 0,
|
||||
Self::ExitInitCheck => 0,
|
||||
Self::InterpreterExit => 0,
|
||||
Self::LoadAssertionError => 0,
|
||||
Self::LoadLocals => 0,
|
||||
Self::ReturnGenerator => 0,
|
||||
Self::StoreSlice => 0,
|
||||
Self::DictMerge { .. } => 0,
|
||||
Self::BuildConstKeyMap { .. } => 0,
|
||||
Self::CopyFreeVars { .. } => 0,
|
||||
Self::EnterExecutor => 0,
|
||||
Self::JumpBackwardNoInterrupt { .. } => 0,
|
||||
Self::JumpBackward { .. } => 0,
|
||||
Self::JumpForward { .. } => 0,
|
||||
Self::ListExtend { .. } => 0,
|
||||
Self::LoadFastCheck(_) => 0,
|
||||
Self::LoadFastLoadFast { .. } => 0,
|
||||
Self::LoadFromDictOrGlobals(_) => 0,
|
||||
Self::SetUpdate { .. } => 0,
|
||||
Self::MakeCell(_) => 0,
|
||||
Self::LoadSuperAttr { .. } => 0,
|
||||
Self::StoreFastStoreFast { .. } => 0,
|
||||
Self::PopJumpIfNone { .. } => 0,
|
||||
Self::PopJumpIfNotNone { .. } => 0,
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
&self,
|
||||
arg: OpArg,
|
||||
f: &mut fmt::Formatter<'_>,
|
||||
ctx: &impl InstrDisplayContext,
|
||||
expand_code_objects: bool,
|
||||
pad: usize,
|
||||
level: usize,
|
||||
) -> fmt::Result {
|
||||
macro_rules! w {
|
||||
($variant:ident) => {
|
||||
write!(f, stringify!($variant))
|
||||
};
|
||||
($variant:ident, $map:ident = $arg_marker:expr) => {{
|
||||
let arg = $arg_marker.get(arg);
|
||||
write!(f, "{:pad$}({}, {})", stringify!($variant), arg, $map(arg))
|
||||
}};
|
||||
($variant:ident, $arg_marker:expr) => {
|
||||
write!(f, "{:pad$}({})", stringify!($variant), $arg_marker.get(arg))
|
||||
};
|
||||
($variant:ident, ?$arg_marker:expr) => {
|
||||
write!(
|
||||
f,
|
||||
"{:pad$}({:?})",
|
||||
stringify!($variant),
|
||||
$arg_marker.get(arg)
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
let varname = |i: u32| ctx.get_varname(i as usize);
|
||||
let name = |i: u32| ctx.get_name(i as usize);
|
||||
let cell_name = |i: u32| ctx.get_cell_name(i as usize);
|
||||
|
||||
let fmt_const =
|
||||
|op: &str, arg: OpArg, f: &mut fmt::Formatter<'_>, idx: &Arg<u32>| -> fmt::Result {
|
||||
let value = ctx.get_constant(idx.get(arg) as usize);
|
||||
match value.borrow_constant() {
|
||||
BorrowedConstant::Code { code } if expand_code_objects => {
|
||||
write!(f, "{op:pad$}({code:?}):")?;
|
||||
code.display_inner(f, true, level + 1)?;
|
||||
Ok(())
|
||||
}
|
||||
c => {
|
||||
write!(f, "{op:pad$}(")?;
|
||||
c.fmt_display(f)?;
|
||||
write!(f, ")")
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match self {
|
||||
Self::BeforeAsyncWith => w!(BEFORE_ASYNC_WITH),
|
||||
Self::BeforeWith => w!(BEFORE_WITH),
|
||||
Self::BinaryOp { op } => write!(f, "{:pad$}({})", "BINARY_OP", op.get(arg)),
|
||||
Self::BinarySubscr => w!(BINARY_SUBSCR),
|
||||
Self::Break { target } => w!(BREAK, target),
|
||||
Self::BuildList { size } => w!(BUILD_LIST, size),
|
||||
Self::BuildListFromTuples { size } => w!(BUILD_LIST_FROM_TUPLES, size),
|
||||
Self::BuildMap { size } => w!(BUILD_MAP, size),
|
||||
Self::BuildMapForCall { size } => w!(BUILD_MAP_FOR_CALL, size),
|
||||
Self::BuildSet { size } => w!(BUILD_SET, size),
|
||||
Self::BuildSetFromTuples { size } => w!(BUILD_SET_FROM_TUPLES, size),
|
||||
Self::BuildSlice { argc } => w!(BUILD_SLICE, ?argc),
|
||||
Self::BuildString { size } => w!(BUILD_STRING, size),
|
||||
Self::BuildTuple { size } => w!(BUILD_TUPLE, size),
|
||||
Self::BuildTupleFromIter => w!(BUILD_TUPLE_FROM_ITER),
|
||||
Self::BuildTupleFromTuples { size } => w!(BUILD_TUPLE_FROM_TUPLES, size),
|
||||
Self::Call { nargs } => w!(CALL, nargs),
|
||||
Self::CallFunctionEx { has_kwargs } => w!(CALL_FUNCTION_EX, has_kwargs),
|
||||
Self::CallKw { nargs } => w!(CALL_KW, nargs),
|
||||
Self::CallIntrinsic1 { func } => w!(CALL_INTRINSIC_1, ?func),
|
||||
Self::CallIntrinsic2 { func } => w!(CALL_INTRINSIC_2, ?func),
|
||||
Self::CheckEgMatch => w!(CHECK_EG_MATCH),
|
||||
Self::CheckExcMatch => w!(CHECK_EXC_MATCH),
|
||||
Self::CleanupThrow => w!(CLEANUP_THROW),
|
||||
Self::CompareOp { op } => w!(COMPARE_OP, ?op),
|
||||
Self::ContainsOp(inv) => w!(CONTAINS_OP, ?inv),
|
||||
Self::Continue { target } => w!(CONTINUE, target),
|
||||
Self::ConvertValue { oparg } => write!(f, "{:pad$}{}", "CONVERT_VALUE", oparg.get(arg)),
|
||||
Self::Copy { index } => w!(COPY, index),
|
||||
Self::DeleteAttr { idx } => w!(DELETE_ATTR, name = idx),
|
||||
Self::DeleteDeref(idx) => w!(DELETE_DEREF, cell_name = idx),
|
||||
Self::DeleteFast(idx) => w!(DELETE_FAST, varname = idx),
|
||||
Self::DeleteGlobal(idx) => w!(DELETE_GLOBAL, name = idx),
|
||||
Self::DeleteName(idx) => w!(DELETE_NAME, name = idx),
|
||||
Self::DeleteSubscr => w!(DELETE_SUBSCR),
|
||||
Self::DictUpdate { index } => w!(DICT_UPDATE, index),
|
||||
Self::EndAsyncFor => w!(END_ASYNC_FOR),
|
||||
Self::EndSend => w!(END_SEND),
|
||||
Self::ExtendedArg => w!(EXTENDED_ARG, Arg::<u32>::marker()),
|
||||
Self::ForIter { target } => w!(FOR_ITER, target),
|
||||
Self::FormatSimple => w!(FORMAT_SIMPLE),
|
||||
Self::FormatWithSpec => w!(FORMAT_WITH_SPEC),
|
||||
Self::GetAIter => w!(GET_AITER),
|
||||
Self::GetANext => w!(GET_ANEXT),
|
||||
Self::GetAwaitable => w!(GET_AWAITABLE),
|
||||
Self::Reserved => w!(RESERVED),
|
||||
Self::GetIter => w!(GET_ITER),
|
||||
Self::GetLen => w!(GET_LEN),
|
||||
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),
|
||||
Self::JumpIfFalseOrPop { target } => w!(JUMP_IF_FALSE_OR_POP, target),
|
||||
Self::JumpIfNotExcMatch(target) => w!(JUMP_IF_NOT_EXC_MATCH, target),
|
||||
Self::JumpIfTrueOrPop { target } => w!(JUMP_IF_TRUE_OR_POP, target),
|
||||
Self::ListAppend { i } => w!(LIST_APPEND, i),
|
||||
Self::LoadAttr { idx } => {
|
||||
let encoded = idx.get(arg);
|
||||
let (name_idx, is_method) = decode_load_attr_arg(encoded);
|
||||
let attr_name = name(name_idx);
|
||||
if is_method {
|
||||
write!(
|
||||
f,
|
||||
"{:pad$}({}, {}, method=true)",
|
||||
"LOAD_ATTR", encoded, attr_name
|
||||
)
|
||||
} else {
|
||||
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),
|
||||
Self::LoadConst { idx } => fmt_const("LOAD_CONST", arg, f, idx),
|
||||
Self::LoadDeref(idx) => w!(LOAD_DEREF, cell_name = idx),
|
||||
Self::LoadFast(idx) => w!(LOAD_FAST, varname = idx),
|
||||
Self::LoadFastAndClear(idx) => w!(LOAD_FAST_AND_CLEAR, varname = idx),
|
||||
Self::LoadGlobal(idx) => w!(LOAD_GLOBAL, name = idx),
|
||||
Self::LoadName(idx) => w!(LOAD_NAME, name = idx),
|
||||
Self::MakeFunction => w!(MAKE_FUNCTION),
|
||||
Self::MapAdd { i } => w!(MAP_ADD, i),
|
||||
Self::MatchClass(arg) => w!(MATCH_CLASS, arg),
|
||||
Self::MatchKeys => w!(MATCH_KEYS),
|
||||
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),
|
||||
Self::PopTop => w!(POP_TOP),
|
||||
Self::PushExcInfo => w!(PUSH_EXC_INFO),
|
||||
Self::PushNull => w!(PUSH_NULL),
|
||||
Self::RaiseVarargs { kind } => w!(RAISE_VARARGS, ?kind),
|
||||
Self::Reraise { depth } => w!(RERAISE, depth),
|
||||
Self::Resume { arg } => w!(RESUME, arg),
|
||||
Self::ReturnConst { idx } => fmt_const("RETURN_CONST", arg, f, idx),
|
||||
Self::ReturnValue => w!(RETURN_VALUE),
|
||||
Self::Send { target } => w!(SEND, target),
|
||||
Self::SetAdd { i } => w!(SET_ADD, i),
|
||||
Self::SetExcInfo => w!(SET_EXC_INFO),
|
||||
Self::SetFunctionAttribute { attr } => w!(SET_FUNCTION_ATTRIBUTE, ?attr),
|
||||
Self::SetupAnnotations => w!(SETUP_ANNOTATIONS),
|
||||
Self::StoreAttr { idx } => w!(STORE_ATTR, name = idx),
|
||||
Self::StoreDeref(idx) => w!(STORE_DEREF, cell_name = idx),
|
||||
Self::StoreFast(idx) => w!(STORE_FAST, varname = idx),
|
||||
Self::StoreFastLoadFast {
|
||||
store_idx,
|
||||
load_idx,
|
||||
} => {
|
||||
write!(f, "STORE_FAST_LOAD_FAST")?;
|
||||
write!(f, " ({}, {})", store_idx.get(arg), load_idx.get(arg))
|
||||
}
|
||||
Self::StoreGlobal(idx) => w!(STORE_GLOBAL, name = idx),
|
||||
Self::StoreName(idx) => w!(STORE_NAME, name = idx),
|
||||
Self::StoreSubscr => w!(STORE_SUBSCR),
|
||||
Self::Subscript => w!(SUBSCRIPT),
|
||||
Self::Swap { index } => w!(SWAP, index),
|
||||
Self::ToBool => w!(TO_BOOL),
|
||||
Self::UnpackEx { args } => w!(UNPACK_EX, args),
|
||||
Self::UnpackSequence { size } => w!(UNPACK_SEQUENCE, size),
|
||||
Self::WithExceptStart => w!(WITH_EXCEPT_START),
|
||||
Self::UnaryInvert => w!(UNARY_INVERT),
|
||||
Self::UnaryNegative => w!(UNARY_NEGATIVE),
|
||||
Self::UnaryNot => w!(UNARY_NOT),
|
||||
Self::YieldValue { arg } => w!(YIELD_VALUE, arg),
|
||||
Self::GetYieldFromIter => w!(GET_YIELD_FROM_ITER),
|
||||
_ => w!(RUSTPYTHON_PLACEHOLDER),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -762,7 +762,7 @@ impl ExecutingFrame<'_> {
|
||||
bytecode::Instruction::ConvertValue { oparg: conversion } => {
|
||||
self.convert_value(conversion.get(arg), vm)
|
||||
}
|
||||
bytecode::Instruction::CopyItem { index } => {
|
||||
bytecode::Instruction::Copy { index } => {
|
||||
// CopyItem { index: 1 } copies TOS
|
||||
// CopyItem { index: 2 } copies second from top
|
||||
// This is 1-indexed to match CPython
|
||||
|
||||
@@ -10,7 +10,9 @@ import re
|
||||
import typing
|
||||
|
||||
ROOT = pathlib.Path(__file__).parents[1]
|
||||
BYTECODE_FILE = ROOT / "crates" / "compiler-core" / "src" / "bytecode.rs"
|
||||
BYTECODE_FILE = (
|
||||
ROOT / "crates" / "compiler-core" / "src" / "bytecode" / "instruction.rs"
|
||||
)
|
||||
OPCODE_METADATA_FILE = ROOT / "Lib" / "_opcode_metadata.py"
|
||||
|
||||
|
||||
@@ -20,10 +22,7 @@ class Opcode(typing.NamedTuple):
|
||||
|
||||
@property
|
||||
def cpython_name(self) -> str:
|
||||
if self.rust_name == "CopyItem":
|
||||
return "COPY"
|
||||
name = self.rust_name
|
||||
name = re.sub(r"(?<=[a-z0-9])([A-Z])", r"_\1", name)
|
||||
name = re.sub(r"(?<=[a-z0-9])([A-Z])", r"_\1", self.rust_name)
|
||||
return re.sub(r"(\D)(\d+)$", r"\1_\2", name).upper()
|
||||
|
||||
@classmethod
|
||||
|
||||
Reference in new issue
Block a user