diff --git a/Lib/_opcode_metadata.py b/Lib/_opcode_metadata.py index 34e73bf31b4..efb5f21e7fb 100644 --- a/Lib/_opcode_metadata.py +++ b/Lib/_opcode_metadata.py @@ -233,7 +233,6 @@ 'INSTRUMENTED_POP_JUMP_IF_NONE': 252, 'INSTRUMENTED_POP_JUMP_IF_NOT_NONE': 253, 'INSTRUMENTED_LINE': 254, - 'LOAD_CLOSURE': 255, 'JUMP': 256, 'JUMP_NO_INTERRUPT': 257, 'RESERVED_258': 258, @@ -246,6 +245,7 @@ 'SETUP_FINALLY': 265, 'SETUP_WITH': 266, 'STORE_FAST_MAYBE_NULL': 267, + 'LOAD_CLOSURE': 268, } # CPython 3.13 compatible: opcodes < 44 have no argument diff --git a/Lib/test/test__opcode.py b/Lib/test/test__opcode.py index 045e010db4c..9d9d7f95f29 100644 --- a/Lib/test/test__opcode.py +++ b/Lib/test/test__opcode.py @@ -16,7 +16,6 @@ def check_bool_function_result(self, func, ops, expected): self.assertIsInstance(func(op), bool) self.assertEqual(func(op), expected) - @unittest.expectedFailure # TODO: RUSTPYTHON; Move LoadClosure to psudoes def test_invalid_opcodes(self): invalid = [-100, -1, 255, 512, 513, 1000] self.check_bool_function_result(_opcode.is_valid, invalid, False) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index be7807ec411..a76d2b3446b 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -4074,7 +4074,7 @@ impl Compiler { } }; - emit!(self, Instruction::LoadClosure(idx.to_u32())); + emit!(self, PseudoInstruction::LoadClosure(idx.to_u32())); } // Build tuple of closure variables @@ -4297,7 +4297,7 @@ impl Compiler { .position(|var| *var == "__class__"); if let Some(classcell_idx) = classcell_idx { - emit!(self, Instruction::LoadClosure(classcell_idx.to_u32())); + emit!(self, PseudoInstruction::LoadClosure(classcell_idx.to_u32())); emit!(self, Instruction::Copy { index: 1_u32 }); let classcell = self.name("__classcell__"); emit!(self, Instruction::StoreName(classcell)); diff --git a/crates/codegen/src/ir.rs b/crates/codegen/src/ir.rs index bb350abeebf..8d0d1f7f73c 100644 --- a/crates/codegen/src/ir.rs +++ b/crates/codegen/src/ir.rs @@ -263,6 +263,13 @@ impl CodeInfo { info.arg = OpArg(encoded); info.instr = Instruction::LoadSuperAttr { arg: Arg::marker() }.into(); } + // LOAD_CLOSURE pseudo → LOAD_FAST (with varnames offset) + PseudoInstruction::LoadClosure(idx) => { + let varnames_len = varname_cache.len() as u32; + let new_idx = varnames_len + idx.get(info.arg); + info.arg = OpArg(new_idx); + info.instr = Instruction::LoadFast(Arg::marker()).into(); + } PseudoInstruction::Jump { .. } => { // PseudoInstruction::Jump instructions are handled later } diff --git a/crates/compiler-core/src/bytecode/instruction.rs b/crates/compiler-core/src/bytecode/instruction.rs index 4faf67d273b..37a015ce76d 100644 --- a/crates/compiler-core/src/bytecode/instruction.rs +++ b/crates/compiler-core/src/bytecode/instruction.rs @@ -383,8 +383,6 @@ pub enum Instruction { InstrumentedPopJumpIfNone = 252, // Placeholder InstrumentedPopJumpIfNotNone = 253, // Placeholder InstrumentedLine = 254, // Placeholder - // Pseudos (needs to be moved to `PseudoInstruction` enum. - LoadClosure(Arg) = 255, // TODO: Move to pseudos } const _: () = assert!(mem::size_of::() == 1); @@ -415,9 +413,6 @@ impl TryFrom for Instruction { let instrumented_start = u8::from(Self::InstrumentedResume); let instrumented_end = u8::from(Self::InstrumentedLine); - // TODO: Remove this; This instruction needs to be pseudo - let load_closure = u8::from(Self::LoadClosure(Arg::marker())); - // RustPython-only opcodes (explicit list to avoid gaps like 125-127) let custom_ops: &[u8] = &[ u8::from(Self::Break { @@ -457,7 +452,6 @@ impl TryFrom for Instruction { if (cpython_start..=cpython_end).contains(&value) || value == resume_id - || value == load_closure || custom_ops.contains(&value) || (specialized_start..=specialized_end).contains(&value) || (instrumented_start..=instrumented_end).contains(&value) @@ -704,7 +698,6 @@ impl InstructionMetadata for Instruction { Self::StoreFastStoreFast { .. } => 0, Self::PopJumpIfNone { .. } => 0, Self::PopJumpIfNotNone { .. } => 0, - Self::LoadClosure(_) => 1, Self::BinaryOpAddFloat => 0, Self::BinaryOpAddInt => 0, Self::BinaryOpAddUnicode => 0, @@ -935,7 +928,6 @@ impl InstructionMetadata for Instruction { } 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), @@ -1037,6 +1029,7 @@ pub enum PseudoInstruction { SetupFinally = 265, // Placeholder SetupWith = 266, // Placeholder StoreFastMaybeNull = 267, // Placeholder + LoadClosure(Arg) = 268, } const _: () = assert!(mem::size_of::() == 2); @@ -1057,7 +1050,7 @@ impl TryFrom for PseudoInstruction { let start = u16::from(Self::Jump { target: Arg::marker(), }); - let end = u16::from(Self::StoreFastMaybeNull); + let end = u16::from(Self::LoadClosure(Arg::marker())); if (start..=end).contains(&value) { Ok(unsafe { mem::transmute::(value) }) @@ -1093,6 +1086,7 @@ impl InstructionMetadata for PseudoInstruction { Self::SetupWith => 0, Self::StoreFastMaybeNull => 0, Self::Reserved258 => 0, + Self::LoadClosure(_) => 1, } } diff --git a/crates/stdlib/src/opcode.rs b/crates/stdlib/src/opcode.rs index fcde0de36ef..92d5d407aff 100644 --- a/crates/stdlib/src/opcode.rs +++ b/crates/stdlib/src/opcode.rs @@ -125,7 +125,6 @@ mod opcode { Ok(AnyInstruction::Real( Instruction::DeleteDeref(_) | Instruction::LoadFromDictOrDeref(_) - | Instruction::LoadClosure(_) | Instruction::LoadDeref(_) | Instruction::StoreDeref(_) )) diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index 55788ee3e4a..a6ef4c5363b 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "flame")] +use crate::bytecode::InstructionMetadata; use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, builtins::{ @@ -137,10 +139,24 @@ impl Frame { func_obj: Option, vm: &VirtualMachine, ) -> Self { - let cells_frees = core::iter::repeat_with(|| PyCell::default().into_ref(&vm.ctx)) - .take(code.cellvars.len()) - .chain(closure.iter().cloned()) - .collect(); + let nlocals = code.varnames.len(); + let num_cells = code.cellvars.len(); + let nfrees = closure.len(); + + let cells_frees: Box<[PyCellRef]> = + core::iter::repeat_with(|| PyCell::default().into_ref(&vm.ctx)) + .take(num_cells) + .chain(closure.iter().cloned()) + .collect(); + + // Extend fastlocals to include varnames + cellvars + freevars (localsplus) + let total_locals = nlocals + num_cells + nfrees; + let mut fastlocals_vec: Vec> = vec![None; total_locals]; + + // Store cell objects at cellvars and freevars positions + for (i, cell) in cells_frees.iter().enumerate() { + fastlocals_vec[nlocals + i] = Some(cell.clone().into()); + } let state = FrameState { stack: BoxVec::new(code.max_stackdepth as usize), @@ -149,7 +165,7 @@ impl Frame { }; Self { - fastlocals: PyMutex::new(vec![None; code.varnames.len()].into_boxed_slice()), + fastlocals: PyMutex::new(fastlocals_vec.into_boxed_slice()), cells_frees, locals: scope.locals, globals: scope.globals, @@ -1213,11 +1229,6 @@ impl ExecutingFrame<'_> { }); Ok(None) } - Instruction::LoadClosure(i) => { - let value = self.cells_frees[i.get(arg) as usize].clone(); - self.push_value(value.into()); - Ok(None) - } Instruction::LoadConst { idx } => { self.push_value(self.code.constants[idx.get(arg) as usize].clone().into()); Ok(None)