Skip to content

Commit 6c39584

Browse files
committed
Add localspluskinds, unify DEREF to localsplus index
- Add CO_FAST_LOCAL/CELL/FREE/HIDDEN constants and localspluskinds field to CodeObject for per-slot metadata - Change DEREF instruction opargs from cell-relative indices (NameIdx) to localsplus absolute indices (oparg::VarNum) - Add fixup_deref_opargs pass in ir.rs to convert cell-relative indices to localsplus indices after finalization - Replace get_cell_name with get_localsplus_name in InstrDisplayContext trait - Update VM cell_ref/get_cell_contents/set_cell_contents to use localsplus indices directly (no nlocals offset) - Update function.rs cell2arg, super.rs __class__ lookup with explicit nlocals offsets
1 parent 7645157 commit 6c39584

File tree

10 files changed

+190
-71
lines changed

10 files changed

+190
-71
lines changed

crates/codegen/src/compile.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -692,26 +692,28 @@ impl Compiler {
692692
.expect("symbol_table_stack is empty! This is a compiler bug.")
693693
}
694694

695-
/// Get the index of a free variable.
696-
fn get_free_var_index(&mut self, name: &str) -> CompileResult<u32> {
695+
/// Get the cell-relative index of a free variable.
696+
/// Returns ncells + freevar_idx. Fixed up to localsplus index during finalize.
697+
fn get_free_var_index(&mut self, name: &str) -> CompileResult<oparg::VarNum> {
697698
let info = self.code_stack.last_mut().unwrap();
698699
let idx = info
699700
.metadata
700701
.freevars
701702
.get_index_of(name)
702703
.unwrap_or_else(|| info.metadata.freevars.insert_full(name.to_owned()).0);
703-
Ok((idx + info.metadata.cellvars.len()).to_u32())
704+
Ok((idx + info.metadata.cellvars.len()).to_u32().into())
704705
}
705706

706-
/// Get the index of a cell variable.
707-
fn get_cell_var_index(&mut self, name: &str) -> CompileResult<u32> {
707+
/// Get the cell-relative index of a cell variable.
708+
/// Returns cellvar_idx. Fixed up to localsplus index during finalize.
709+
fn get_cell_var_index(&mut self, name: &str) -> CompileResult<oparg::VarNum> {
708710
let info = self.code_stack.last_mut().unwrap();
709711
let idx = info
710712
.metadata
711713
.cellvars
712714
.get_index_of(name)
713715
.unwrap_or_else(|| info.metadata.cellvars.insert_full(name.to_owned()).0);
714-
Ok(idx.to_u32())
716+
Ok(idx.to_u32().into())
715717
}
716718

717719
/// Get the index of a local variable.
@@ -8004,7 +8006,7 @@ impl Compiler {
80048006
// For CELL variables, we additionally push the cell content via MakeCell
80058007
// (which saves the old cell value and clears the cell for the comprehension).
80068008
// Track cell indices to restore them later.
8007-
let mut cell_indices: Vec<Option<u32>> = Vec::new();
8009+
let mut cell_indices: Vec<Option<oparg::VarNum>> = Vec::new();
80088010
let mut total_stack_items: usize = 0;
80098011
for name in &pushed_locals {
80108012
let var_num = self.varname(name)?;

crates/codegen/src/ir.rs

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ use num_traits::ToPrimitive;
88
use rustpython_compiler_core::{
99
OneIndexed, SourceLocation,
1010
bytecode::{
11-
AnyInstruction, Arg, CodeFlags, CodeObject, CodeUnit, CodeUnits, ConstantData,
12-
ExceptionTableEntry, InstrDisplayContext, Instruction, InstructionMetadata, Label, OpArg,
13-
PseudoInstruction, PyCodeLocationInfoKind, encode_exception_table, oparg,
11+
AnyInstruction, Arg, CO_FAST_CELL, CO_FAST_FREE, CO_FAST_LOCAL, CodeFlags, CodeObject,
12+
CodeUnit, CodeUnits, ConstantData, ExceptionTableEntry, InstrDisplayContext, Instruction,
13+
InstructionMetadata, Label, OpArg, PseudoInstruction, PyCodeLocationInfoKind,
14+
encode_exception_table, oparg,
1415
},
1516
varint::{write_signed_varint, write_varint},
1617
};
@@ -255,6 +256,8 @@ impl CodeInfo {
255256

256257
// Convert pseudo ops and remove resulting NOPs (keep line-marker NOPs)
257258
convert_pseudo_ops(&mut blocks, varname_cache.len() as u32);
259+
// Fixup DEREF opargs: cell-relative → localsplus indices
260+
fixup_deref_opargs(&mut blocks, varname_cache.len() as u32);
258261
// Remove redundant NOPs, keeping line-marker NOPs only when
259262
// they are needed to preserve tracing.
260263
let mut block_order = Vec::new();
@@ -488,6 +491,15 @@ impl CodeInfo {
488491
// Generate exception table before moving source_path
489492
let exceptiontable = generate_exception_table(&blocks, &block_to_index);
490493

494+
// Build localspluskinds: per-slot kind flags
495+
let nlocals = varname_cache.len();
496+
let ncells = cellvar_cache.len();
497+
let nfrees = freevar_cache.len();
498+
let mut localspluskinds = Vec::with_capacity(nlocals + ncells + nfrees);
499+
localspluskinds.extend(core::iter::repeat_n(CO_FAST_LOCAL, nlocals));
500+
localspluskinds.extend(core::iter::repeat_n(CO_FAST_CELL, ncells));
501+
localspluskinds.extend(core::iter::repeat_n(CO_FAST_FREE, nfrees));
502+
491503
Ok(CodeObject {
492504
flags,
493505
posonlyarg_count,
@@ -507,6 +519,7 @@ impl CodeInfo {
507519
cellvars: cellvar_cache.into_iter().collect(),
508520
freevars: freevar_cache.into_iter().collect(),
509521
cell2arg,
522+
localspluskinds: localspluskinds.into_boxed_slice(),
510523
linetable,
511524
exceptiontable,
512525
})
@@ -1113,12 +1126,19 @@ impl InstrDisplayContext for CodeInfo {
11131126
self.metadata.varnames[var_num.as_usize()].as_ref()
11141127
}
11151128

1116-
fn get_cell_name(&self, i: usize) -> &str {
1117-
self.metadata
1118-
.cellvars
1119-
.get_index(i)
1120-
.unwrap_or_else(|| &self.metadata.freevars[i - self.metadata.cellvars.len()])
1121-
.as_ref()
1129+
fn get_localsplus_name(&self, var_num: oparg::VarNum) -> &str {
1130+
let idx = var_num.as_usize();
1131+
let nlocals = self.metadata.varnames.len();
1132+
if idx < nlocals {
1133+
self.metadata.varnames[idx].as_ref()
1134+
} else {
1135+
let cell_idx = idx - nlocals;
1136+
self.metadata
1137+
.cellvars
1138+
.get_index(cell_idx)
1139+
.unwrap_or_else(|| &self.metadata.freevars[cell_idx - self.metadata.cellvars.len()])
1140+
.as_ref()
1141+
}
11221142
}
11231143
}
11241144

@@ -1814,3 +1834,28 @@ pub(crate) fn convert_pseudo_ops(blocks: &mut [Block], varnames_len: u32) {
18141834
}
18151835
}
18161836
}
1837+
1838+
/// Convert DEREF instruction opargs from cell-relative indices to localsplus indices.
1839+
/// Cell-relative index = cellvar_idx for cells, ncells + freevar_idx for frees.
1840+
/// Localsplus index = nlocals + cell_relative_idx.
1841+
pub(crate) fn fixup_deref_opargs(blocks: &mut [Block], nlocals: u32) {
1842+
for block in blocks.iter_mut() {
1843+
for info in &mut block.instructions {
1844+
let Some(instr) = info.instr.real() else {
1845+
continue;
1846+
};
1847+
let needs_fixup = matches!(
1848+
instr,
1849+
Instruction::LoadDeref { .. }
1850+
| Instruction::StoreDeref { .. }
1851+
| Instruction::DeleteDeref { .. }
1852+
| Instruction::LoadFromDictOrDeref { .. }
1853+
| Instruction::MakeCell { .. }
1854+
| Instruction::RestoreCell { .. }
1855+
);
1856+
if needs_fixup {
1857+
info.arg = OpArg::new(nlocals + u32::from(info.arg));
1858+
}
1859+
}
1860+
}
1861+
}

crates/compiler-core/src/bytecode.rs

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,12 @@ impl<T> IndexMut<oparg::VarNum> for [T] {
334334
}
335335
}
336336

337+
/// Per-slot kind flags for localsplus (co_localspluskinds).
338+
pub const CO_FAST_HIDDEN: u8 = 0x10;
339+
pub const CO_FAST_LOCAL: u8 = 0x20;
340+
pub const CO_FAST_CELL: u8 = 0x40;
341+
pub const CO_FAST_FREE: u8 = 0x80;
342+
337343
/// Primary container of a single code object. Each python function has
338344
/// a code object. Also a module has a code object.
339345
#[derive(Clone)]
@@ -358,6 +364,9 @@ pub struct CodeObject<C: Constant = ConstantData> {
358364
pub varnames: Box<[C::Name]>,
359365
pub cellvars: Box<[C::Name]>,
360366
pub freevars: Box<[C::Name]>,
367+
/// Per-slot kind flags: CO_FAST_LOCAL, CO_FAST_CELL, CO_FAST_FREE, CO_FAST_HIDDEN.
368+
/// Length = nlocalsplus (nlocals + ncells + nfrees).
369+
pub localspluskinds: Box<[u8]>,
361370
/// Line number table (CPython 3.11+ format)
362371
pub linetable: Box<[u8]>,
363372
/// Exception handling table
@@ -1080,6 +1089,7 @@ impl<C: Constant> CodeObject<C> {
10801089
first_line_number: self.first_line_number,
10811090
max_stackdepth: self.max_stackdepth,
10821091
cell2arg: self.cell2arg,
1092+
localspluskinds: self.localspluskinds,
10831093
linetable: self.linetable,
10841094
exceptiontable: self.exceptiontable,
10851095
}
@@ -1112,6 +1122,7 @@ impl<C: Constant> CodeObject<C> {
11121122
first_line_number: self.first_line_number,
11131123
max_stackdepth: self.max_stackdepth,
11141124
cell2arg: self.cell2arg.clone(),
1125+
localspluskinds: self.localspluskinds.clone(),
11151126
linetable: self.linetable.clone(),
11161127
exceptiontable: self.exceptiontable.clone(),
11171128
}
@@ -1140,7 +1151,8 @@ pub trait InstrDisplayContext {
11401151

11411152
fn get_varname(&self, var_num: oparg::VarNum) -> &str;
11421153

1143-
fn get_cell_name(&self, i: usize) -> &str;
1154+
/// Get name for a localsplus index (used by DEREF instructions).
1155+
fn get_localsplus_name(&self, var_num: oparg::VarNum) -> &str;
11441156
}
11451157

11461158
impl<C: Constant> InstrDisplayContext for CodeObject<C> {
@@ -1158,11 +1170,18 @@ impl<C: Constant> InstrDisplayContext for CodeObject<C> {
11581170
self.varnames[var_num].as_ref()
11591171
}
11601172

1161-
fn get_cell_name(&self, i: usize) -> &str {
1162-
self.cellvars
1163-
.get(i)
1164-
.unwrap_or_else(|| &self.freevars[i - self.cellvars.len()])
1165-
.as_ref()
1173+
fn get_localsplus_name(&self, var_num: oparg::VarNum) -> &str {
1174+
let idx = var_num.as_usize();
1175+
let nlocals = self.varnames.len();
1176+
if idx < nlocals {
1177+
self.varnames[idx].as_ref()
1178+
} else {
1179+
let cell_idx = idx - nlocals;
1180+
self.cellvars
1181+
.get(cell_idx)
1182+
.unwrap_or_else(|| &self.freevars[cell_idx - self.cellvars.len()])
1183+
.as_ref()
1184+
}
11661185
}
11671186
}
11681187

crates/compiler-core/src/bytecode/instruction.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ pub enum Instruction {
130130
namei: Arg<NameIdx>,
131131
} = 61,
132132
DeleteDeref {
133-
i: Arg<NameIdx>,
133+
i: Arg<oparg::VarNum>,
134134
} = 62,
135135
DeleteFast {
136136
var_num: Arg<oparg::VarNum>,
@@ -189,7 +189,7 @@ pub enum Instruction {
189189
consti: Arg<oparg::ConstIdx>,
190190
} = 82,
191191
LoadDeref {
192-
i: Arg<NameIdx>,
192+
i: Arg<oparg::VarNum>,
193193
} = 83,
194194
LoadFast {
195195
var_num: Arg<oparg::VarNum>,
@@ -210,7 +210,7 @@ pub enum Instruction {
210210
var_nums: Arg<oparg::VarNums>,
211211
} = 89,
212212
LoadFromDictOrDeref {
213-
i: Arg<NameIdx>,
213+
i: Arg<oparg::VarNum>,
214214
} = 90,
215215
LoadFromDictOrGlobals {
216216
i: Arg<NameIdx>,
@@ -231,7 +231,7 @@ pub enum Instruction {
231231
namei: Arg<LoadSuperAttr>,
232232
} = 96,
233233
MakeCell {
234-
i: Arg<NameIdx>,
234+
i: Arg<oparg::VarNum>,
235235
} = 97,
236236
MapAdd {
237237
i: Arg<u32>,
@@ -273,7 +273,7 @@ pub enum Instruction {
273273
namei: Arg<NameIdx>,
274274
} = 110,
275275
StoreDeref {
276-
i: Arg<NameIdx>,
276+
i: Arg<oparg::VarNum>,
277277
} = 111,
278278
StoreFast {
279279
var_num: Arg<oparg::VarNum>,
@@ -307,7 +307,7 @@ pub enum Instruction {
307307
/// cell slot at `fastlocals[nlocals + i]`, replacing the temporary cell
308308
/// created by `MakeCell`.
309309
RestoreCell {
310-
i: Arg<NameIdx>,
310+
i: Arg<oparg::VarNum>,
311311
} = 121,
312312
// CPython 3.14 RESUME (128)
313313
Resume {
@@ -1130,7 +1130,7 @@ impl InstructionMetadata for Instruction {
11301130

11311131
let varname = |var_num: oparg::VarNum| ctx.get_varname(var_num);
11321132
let name = |i: u32| ctx.get_name(i as usize);
1133-
let cell_name = |i: u32| ctx.get_cell_name(i as usize);
1133+
let cell_name = |i: oparg::VarNum| ctx.get_localsplus_name(i);
11341134

11351135
let fmt_const = |op: &str,
11361136
arg: OpArg,

crates/compiler-core/src/marshal.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,19 @@ pub fn deserialize_code<R: Read, Bag: ConstantBag>(
267267
.to_vec()
268268
.into_boxed_slice();
269269

270+
// Build localspluskinds from varnames/cellvars/freevars
271+
let localspluskinds = {
272+
use crate::bytecode::*;
273+
let nlocals = varnames.len();
274+
let ncells = cellvars.len();
275+
let nfrees = freevars.len();
276+
let mut kinds = Vec::with_capacity(nlocals + ncells + nfrees);
277+
kinds.extend(core::iter::repeat_n(CO_FAST_LOCAL, nlocals));
278+
kinds.extend(core::iter::repeat_n(CO_FAST_CELL, ncells));
279+
kinds.extend(core::iter::repeat_n(CO_FAST_FREE, nfrees));
280+
kinds.into_boxed_slice()
281+
};
282+
270283
Ok(CodeObject {
271284
instructions,
272285
locations,
@@ -285,6 +298,7 @@ pub fn deserialize_code<R: Read, Bag: ConstantBag>(
285298
varnames,
286299
cellvars,
287300
freevars,
301+
localspluskinds,
288302
linetable,
289303
exceptiontable,
290304
})

crates/vm/src/builtins/code.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,19 @@ impl Constructor for PyCode {
564564
)],
565565
> = vec![(loc, loc); instructions.len()].into_boxed_slice();
566566

567+
// Build localspluskinds before moving varnames/cellvars/freevars
568+
let localspluskinds = {
569+
use rustpython_compiler_core::bytecode::*;
570+
let nlocals = varnames.len();
571+
let ncells = cellvars.len();
572+
let nfrees = freevars.len();
573+
let mut kinds = Vec::with_capacity(nlocals + ncells + nfrees);
574+
kinds.extend(core::iter::repeat_n(CO_FAST_LOCAL, nlocals));
575+
kinds.extend(core::iter::repeat_n(CO_FAST_CELL, ncells));
576+
kinds.extend(core::iter::repeat_n(CO_FAST_FREE, nfrees));
577+
kinds.into_boxed_slice()
578+
};
579+
567580
// Build the CodeObject
568581
let code = CodeObject {
569582
instructions,
@@ -587,6 +600,7 @@ impl Constructor for PyCode {
587600
varnames,
588601
cellvars,
589602
freevars,
603+
localspluskinds,
590604
linetable: args.linetable.as_bytes().to_vec().into_boxed_slice(),
591605
exceptiontable: args.exceptiontable.as_bytes().to_vec().into_boxed_slice(),
592606
};
@@ -1156,6 +1170,7 @@ impl PyCode {
11561170
cellvars,
11571171
freevars,
11581172
cell2arg: self.code.cell2arg.clone(),
1173+
localspluskinds: self.code.localspluskinds.clone(),
11591174
linetable,
11601175
exceptiontable,
11611176
};

crates/vm/src/builtins/function.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -442,9 +442,10 @@ impl PyFunction {
442442
}
443443

444444
if let Some(cell2arg) = code.cell2arg.as_deref() {
445+
let nlocals = code.varnames.len();
445446
for (cell_idx, arg_idx) in cell2arg.iter().enumerate().filter(|(_, i)| **i != -1) {
446447
let x = fastlocals[*arg_idx as usize].take();
447-
frame.set_cell_contents(cell_idx, x);
448+
frame.set_cell_contents(nlocals + cell_idx, x);
448449
}
449450
}
450451

@@ -724,10 +725,11 @@ impl Py<PyFunction> {
724725
}
725726

726727
if let Some(cell2arg) = code.cell2arg.as_deref() {
728+
let nlocals = code.varnames.len();
727729
let fastlocals = unsafe { frame.fastlocals_mut() };
728730
for (cell_idx, arg_idx) in cell2arg.iter().enumerate().filter(|(_, i)| **i != -1) {
729731
let x = fastlocals[*arg_idx as usize].take();
730-
frame.set_cell_contents(cell_idx, x);
732+
frame.set_cell_contents(nlocals + cell_idx, x);
731733
}
732734
}
733735

crates/vm/src/builtins/super.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ impl Initializer for PySuper {
8686
return Err(vm.new_runtime_error("super(): no arguments"));
8787
}
8888
// SAFETY: Frame is current and not concurrently mutated.
89+
let nlocals = frame.code.varnames.len();
8990
let obj = unsafe { frame.fastlocals() }[0]
9091
.clone()
9192
.or_else(|| {
@@ -94,7 +95,7 @@ impl Initializer for PySuper {
9495
.iter()
9596
.enumerate()
9697
.find(|(_, arg_idx)| **arg_idx == 0)
97-
.and_then(|(cell_idx, _)| frame.get_cell_contents(cell_idx))
98+
.and_then(|(cell_idx, _)| frame.get_cell_contents(nlocals + cell_idx))
9899
} else {
99100
None
100101
}
@@ -104,7 +105,7 @@ impl Initializer for PySuper {
104105
let mut typ = None;
105106
for (i, var) in frame.code.freevars.iter().enumerate() {
106107
if var.as_bytes() == b"__class__" {
107-
let i = frame.code.cellvars.len() + i;
108+
let i = nlocals + frame.code.cellvars.len() + i;
108109
let class = frame
109110
.get_cell_contents(i)
110111
.ok_or_else(|| vm.new_runtime_error("super(): empty __class__ cell"))?;

0 commit comments

Comments
 (0)