Pseudo ops (#6678)

* better expect

* pseudo instruction

* Fix CallKw narg

* Auto-format: cargo fmt --all

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Jeong, YunWonandgithub-actions[bot] authored and GitHub committed 2026-01-10 19:49:58 +09:00
1 parent 2d3ef86231
commit aa9fc7fede
6 files changed
+172 -61

No files matched your search

+20 -10
View File
@@ -6212,23 +6212,33 @@ impl Compiler {
}
emit!(self, Instruction::CallFunctionEx { has_kwargs });
} else if !arguments.keywords.is_empty() {
let mut kwarg_names = vec![];
// No **kwargs in this branch (has_double_star is false),
// so all keywords have arg.is_some()
let mut kwarg_names = Vec::with_capacity(arguments.keywords.len());
for keyword in &arguments.keywords {
if let Some(name) = &keyword.arg {
kwarg_names.push(ConstantData::Str {
value: name.as_str().into(),
});
} else {
// This means **kwargs!
panic!("name must be set");
}
let name = keyword
.arg
.as_ref()
.expect("has_double_star is false, so arg must be Some");
kwarg_names.push(ConstantData::Str {
value: name.as_str().into(),
});
self.compile_expression(&keyword.value)?;
}
self.emit_load_const(ConstantData::Tuple {
elements: kwarg_names,
});
emit!(self, Instruction::CallKw { nargs: count });
// nargs = positional args + keyword args
let positional = additional_positional
.checked_add(u32::try_from(arguments.args.len()).expect("too many positional args"))
.expect("too many positional args");
let keyword_count =
u32::try_from(arguments.keywords.len()).expect("too many keyword args");
let nargs = positional
.checked_add(keyword_count)
.expect("too many arguments");
emit!(self, Instruction::CallKw { nargs });
} else {
emit!(self, Instruction::Call { nargs: count });
}
+56 -7
View File
@@ -4,9 +4,9 @@ use crate::{IndexMap, IndexSet, error::InternalError};
use rustpython_compiler_core::{
OneIndexed, SourceLocation,
bytecode::{
CodeFlags, CodeObject, CodeUnit, CodeUnits, ConstantData, ExceptionTableEntry,
Arg, CodeFlags, CodeObject, CodeUnit, CodeUnits, ConstantData, ExceptionTableEntry,
InstrDisplayContext, Instruction, Label, OpArg, PyCodeLocationInfoKind,
encode_exception_table,
encode_exception_table, encode_load_attr_arg,
},
varint::{write_signed_varint, write_varint},
};
@@ -189,6 +189,34 @@ impl CodeInfo {
let mut instructions = Vec::new();
let mut locations = Vec::new();
// convert_pseudo_ops: instructions before the main loop
for block in blocks
.iter_mut()
.filter(|b| b.next != BlockIdx::NULL || !b.instructions.is_empty())
{
for info in &mut block.instructions {
match info.instr {
// LOAD_ATTR_METHOD pseudo → LOAD_ATTR (with method flag=1)
Instruction::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() };
}
// POP_BLOCK pseudo → NOP
Instruction::PopBlock => {
info.instr = Instruction::Nop;
}
_ => {}
}
}
}
let mut block_to_offset = vec![Label(0); blocks.len()];
// block_to_index: maps block idx to instruction index (for exception table)
// This is the index into the final instructions array, including EXTENDED_ARG
@@ -213,23 +241,44 @@ impl CodeInfo {
let mut next_block = BlockIdx(0);
while next_block != BlockIdx::NULL {
let block = &mut blocks[next_block];
// Track current instruction offset for jump direction resolution
let mut current_offset = block_to_offset[next_block.idx()].0;
for info in &mut block.instructions {
let (op, arg, target) = (info.instr, &mut info.arg, info.target);
let target = info.target;
if target != BlockIdx::NULL {
let new_arg = OpArg(block_to_offset[target.idx()].0);
recompile_extended_arg |= new_arg.instr_size() != arg.instr_size();
*arg = new_arg;
recompile_extended_arg |= new_arg.instr_size() != info.arg.instr_size();
info.arg = new_arg;
}
let (extras, lo_arg) = arg.split();
// Convert JUMP pseudo to real instructions (direction depends on offset)
let op = match info.instr {
Instruction::Jump { .. } if target != BlockIdx::NULL => {
let target_offset = block_to_offset[target.idx()].0;
if target_offset > current_offset {
Instruction::JumpForward {
target: Arg::marker(),
}
} else {
Instruction::JumpBackward {
target: Arg::marker(),
}
}
}
other => other,
};
let (extras, lo_arg) = info.arg.split();
locations.extend(core::iter::repeat_n(
(info.location, info.end_location),
arg.instr_size(),
info.arg.instr_size(),
));
instructions.extend(
extras
.map(|byte| CodeUnit::new(Instruction::ExtendedArg, byte))
.chain([CodeUnit { op, arg: lo_arg }]),
);
current_offset += info.arg.instr_size() as u32;
}
next_block = block.next;
}
@@ -18,7 +18,7 @@ expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIter
12 STORE_FAST (0, stop_exc)
3 13 LOAD_GLOBAL (2, self)
14 LOAD_ATTR_METHOD (3, subTest)
14 LOAD_ATTR (7, subTest, method=true)
15 LOAD_GLOBAL (4, type)
16 PUSH_NULL
17 LOAD_FAST (0, stop_exc)
@@ -37,7 +37,7 @@ expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIter
>> 29 SEND (34)
30 YIELD_VALUE (1)
31 RESUME (3)
32 JUMP (29)
32 JUMP_BACKWARD (29)
33 CLEANUP_THROW
>> 34 END_SEND
35 POP_TOP
@@ -55,11 +55,11 @@ expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIter
>> 45 SEND (50)
46 YIELD_VALUE (1)
47 RESUME (3)
48 JUMP (45)
48 JUMP_BACKWARD (45)
49 CLEANUP_THROW
>> 50 END_SEND
51 POP_TOP
52 JUMP (74)
52 JUMP_FORWARD (74)
53 PUSH_EXC_INFO
54 WITH_EXCEPT_START
55 GET_AWAITABLE
@@ -67,7 +67,7 @@ expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIter
>> 57 SEND (62)
58 YIELD_VALUE (1)
59 RESUME (3)
60 JUMP (57)
60 JUMP_BACKWARD (57)
61 CLEANUP_THROW
>> 62 END_SEND
63 TO_BOOL
@@ -77,11 +77,11 @@ expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIter
67 POP_EXCEPT
68 POP_TOP
69 POP_TOP
70 JUMP (74)
70 JUMP_FORWARD (74)
71 COPY (3)
72 POP_EXCEPT
73 RERAISE (1)
>> 74 JUMP (100)
>> 74 JUMP_FORWARD (100)
75 PUSH_EXC_INFO
76 COPY (1)
@@ -90,12 +90,12 @@ expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIter
79 STORE_FAST (1, ex)
8 80 LOAD_GLOBAL (2, self)
81 LOAD_ATTR_METHOD (7, assertIs)
81 LOAD_ATTR (15, assertIs, method=true)
82 LOAD_FAST (1, ex)
83 LOAD_FAST (0, stop_exc)
84 CALL (2)
85 POP_TOP
86 JUMP (91)
86 JUMP_FORWARD (91)
87 LOAD_CONST (None)
88 STORE_FAST (1, ex)
89 DELETE_FAST (1, ex)
@@ -104,14 +104,14 @@ expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIter
92 LOAD_CONST (None)
93 STORE_FAST (1, ex)
94 DELETE_FAST (1, ex)
95 JUMP (108)
95 JUMP_FORWARD (108)
>> 96 RAISE_VARARGS (ReraiseFromStack)
97 COPY (3)
98 POP_EXCEPT
99 RAISE_VARARGS (ReraiseFromStack)
10 >> 100 LOAD_GLOBAL (2, self)
101 LOAD_ATTR_METHOD (8, fail)
101 LOAD_ATTR (17, fail, method=true)
102 LOAD_FAST (0, stop_exc)
103 FORMAT_SIMPLE
104 LOAD_CONST (" was suppressed")
@@ -125,7 +125,7 @@ expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIter
111 LOAD_CONST (None)
112 CALL (3)
113 POP_TOP
114 JUMP (128)
114 JUMP_FORWARD (128)
115 PUSH_EXC_INFO
116 WITH_EXCEPT_START
117 TO_BOOL
@@ -135,11 +135,11 @@ expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIter
121 POP_EXCEPT
122 POP_TOP
123 POP_TOP
124 JUMP (128)
124 JUMP_FORWARD (128)
125 COPY (3)
126 POP_EXCEPT
127 RERAISE (1)
>> 128 JUMP (11)
>> 128 JUMP_BACKWARD (11)
>> 129 RETURN_CONST (None)
1 MAKE_FUNCTION
+37 -1
View File
@@ -84,6 +84,20 @@ 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)
}
/// Oparg values for [`Instruction::ConvertValue`].
///
/// ## See also
@@ -1734,6 +1748,9 @@ impl Instruction {
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 }
@@ -1760,6 +1777,9 @@ impl Instruction {
matches!(
self,
Jump { .. }
| JumpForward { .. }
| JumpBackward { .. }
| JumpBackwardNoInterrupt { .. }
| Continue { .. }
| Break { .. }
| ReturnValue
@@ -2053,11 +2073,27 @@ impl Instruction {
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 } => w!(LOAD_ATTR, name = idx),
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),
LoadClassDeref(idx) => w!(LOAD_CLASSDEREF, cell_name = idx),
+8 -2
View File
@@ -212,7 +212,10 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> {
match instruction {
Instruction::ReturnValue
| Instruction::ReturnConst { .. }
| Instruction::Jump { .. } => {
| Instruction::Jump { .. }
| Instruction::JumpBackward { .. }
| Instruction::JumpBackwardNoInterrupt { .. }
| Instruction::JumpForward { .. } => {
in_unreachable_code = true;
}
_ => {}
@@ -558,7 +561,10 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> {
}
Instruction::ExtendedArg => Ok(()),
Instruction::Jump { target } => {
Instruction::Jump { target }
| Instruction::JumpBackward { target }
| Instruction::JumpBackwardNoInterrupt { target }
| Instruction::JumpForward { target } => {
let target_block = self.get_or_create_block(target.get(arg));
self.builder.ins().jump(target_block, &[]);
Ok(())
+37 -27
View File
@@ -1082,6 +1082,18 @@ impl ExecutingFrame<'_> {
self.jump(target.get(arg));
Ok(None)
}
bytecode::Instruction::JumpForward { target } => {
self.jump(target.get(arg));
Ok(None)
}
bytecode::Instruction::JumpBackward { target } => {
self.jump(target.get(arg));
Ok(None)
}
bytecode::Instruction::JumpBackwardNoInterrupt { target } => {
self.jump(target.get(arg));
Ok(None)
}
bytecode::Instruction::ListAppend { i } => {
let item = self.pop_value();
let obj = self.nth_value(i.get(arg));
@@ -1093,6 +1105,9 @@ impl ExecutingFrame<'_> {
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::LoadBuildClass => {
self.push_value(vm.builtins.get_attr(identifier!(vm, __build_class__), vm)?);
Ok(None)
@@ -1164,9 +1179,6 @@ impl ExecutingFrame<'_> {
self.push_value(x);
Ok(None)
}
bytecode::Instruction::LoadAttrMethod { idx } => {
self.load_attr_method(vm, idx.get(arg))
}
bytecode::Instruction::LoadName(idx) => {
let name = self.code.names[idx.get(arg) as usize];
let result = self.locals.mapping().subscript(name, vm);
@@ -1401,8 +1413,9 @@ impl ExecutingFrame<'_> {
Ok(None)
}
bytecode::Instruction::Nop => Ok(None),
// PopBlock is now a pseudo-instruction - exception table handles this
bytecode::Instruction::PopBlock => Ok(None),
bytecode::Instruction::PopBlock => {
unreachable!("PopBlock is converted to Nop during compilation")
}
bytecode::Instruction::PopExcept => {
// Pop prev_exc from value stack and restore it
let prev_exc = self.pop_value();
@@ -2488,31 +2501,28 @@ impl ExecutingFrame<'_> {
Ok(None)
}
fn load_attr(&mut self, vm: &VirtualMachine, idx: u32) -> FrameResult {
// Regular attribute access: pop obj, push attr
let attr_name = self.code.names[idx as usize];
let parent = self.pop_value();
let obj = parent.get_attr(attr_name, vm)?;
self.push_value(obj);
Ok(None)
}
fn load_attr_method(&mut self, vm: &VirtualMachine, idx: u32) -> FrameResult {
// Method call: pop obj, push [method, self_or_null]
let attr_name = self.code.names[idx as usize];
fn load_attr(&mut self, vm: &VirtualMachine, oparg: u32) -> FrameResult {
let (name_idx, is_method) = bytecode::decode_load_attr_arg(oparg);
let attr_name = self.code.names[name_idx as usize];
let parent = self.pop_value();
match PyMethod::get(parent, attr_name, vm)? {
PyMethod::Function { target, func } => {
// Method descriptor found: push [method, self]
self.push_value(func);
self.push_value(target);
}
PyMethod::Attribute(val) => {
// Regular attribute: push [attr, NULL]
self.push_value(val);
self.push_null();
if is_method {
// Method call: push [method, self_or_null]
let method = PyMethod::get(parent.clone(), attr_name, vm)?;
match method {
PyMethod::Function { target: _, func } => {
self.push_value(func);
self.push_value(parent);
}
PyMethod::Attribute(val) => {
self.push_value(val);
self.push_null();
}
}
} else {
// Regular attribute access
let obj = parent.get_attr(attr_name, vm)?;
self.push_value(obj);
}
Ok(None)
}