From 725f12d48a0315aa67ff496ecf54ca8608afc1c2 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 10 Jan 2026 00:10:04 +0900 Subject: [PATCH 1/4] better expect --- crates/codegen/src/compile.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 1f12cc87074..3771ba0b7ed 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -6212,16 +6212,17 @@ impl Compiler { } emit!(self, Instruction::CallFunctionEx { has_kwargs }); } else if !arguments.keywords.is_empty() { + // No **kwargs in this branch (has_double_star is false), + // so all keywords have arg.is_some() let mut kwarg_names = vec![]; 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)?; } From ea8f8d1be21f0e46266c1495d74359278f0e1ec4 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 8 Jan 2026 17:45:22 +0900 Subject: [PATCH 2/4] pseudo instruction --- crates/codegen/src/ir.rs | 63 ++++++++++++++++-- ...pile__tests__nested_double_async_with.snap | 28 ++++---- crates/compiler-core/src/bytecode.rs | 38 ++++++++++- crates/jit/src/instructions.rs | 10 ++- crates/vm/src/frame.rs | 64 +++++++++++-------- 5 files changed, 152 insertions(+), 51 deletions(-) diff --git a/crates/codegen/src/ir.rs b/crates/codegen/src/ir.rs index 6cc22b438f5..d29b1704c60 100644 --- a/crates/codegen/src/ir.rs +++ b/crates/codegen/src/ir.rs @@ -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; } diff --git a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap index 44eaca18e11..c4d2ed83796 100644 --- a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap +++ b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap @@ -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 diff --git a/crates/compiler-core/src/bytecode.rs b/crates/compiler-core/src/bytecode.rs index 5020be27c94..812a3c42718 100644 --- a/crates/compiler-core/src/bytecode.rs +++ b/crates/compiler-core/src/bytecode.rs @@ -84,6 +84,20 @@ pub fn find_exception_handler(table: &[u8], offset: u32) -> Option 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 @@ -1740,6 +1754,9 @@ impl Instruction { pub const fn label_arg(&self) -> Option> { match self { Jump { target: l } + | JumpBackward { target: l } + | JumpBackwardNoInterrupt { target: l } + | JumpForward { target: l } | JumpIfNotExcMatch(l) | PopJumpIfTrue { target: l } | PopJumpIfFalse { target: l } @@ -1766,6 +1783,9 @@ impl Instruction { matches!( self, Jump { .. } + | JumpForward { .. } + | JumpBackward { .. } + | JumpBackwardNoInterrupt { .. } | Continue { .. } | Break { .. } | ReturnValue @@ -2060,11 +2080,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), diff --git a/crates/jit/src/instructions.rs b/crates/jit/src/instructions.rs index 055528db7f7..1901d3e8a49 100644 --- a/crates/jit/src/instructions.rs +++ b/crates/jit/src/instructions.rs @@ -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(()) diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index 7e04c900e26..e589c2c1cc6 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -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(); @@ -2493,31 +2506,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) } From 68a8060329e8f615c86bf055bca5e2e9adc8ee4c Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 10 Jan 2026 15:57:17 +0900 Subject: [PATCH 3/4] Fix CallKw narg --- crates/codegen/src/compile.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 3771ba0b7ed..d5fbd194866 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -6214,7 +6214,7 @@ impl Compiler { } else if !arguments.keywords.is_empty() { // No **kwargs in this branch (has_double_star is false), // so all keywords have arg.is_some() - let mut kwarg_names = vec![]; + let mut kwarg_names = Vec::with_capacity(arguments.keywords.len()); for keyword in &arguments.keywords { let name = keyword .arg @@ -6229,7 +6229,16 @@ impl Compiler { 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 }); } From ce93c4f1f1689e8c29811117b5e8cea715fc03b9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 10 Jan 2026 09:53:38 +0000 Subject: [PATCH 4/4] Auto-format: cargo fmt --all --- Lib/_opcode_metadata.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Lib/_opcode_metadata.py b/Lib/_opcode_metadata.py index 5cfb2f40f34..5734d6141a4 100644 --- a/Lib/_opcode_metadata.py +++ b/Lib/_opcode_metadata.py @@ -4,10 +4,6 @@ _specializations = {} - - - - _specialized_opmap = {} opmap = {