code object linetable (#6150)

* Code.replace

* implement linetable
This commit is contained in:
Jeong, YunWon authored and GitHub committed 2025-09-16 21:49:54 +09:00
1 parent 6ead82154e
commit b666c52df9
6 files changed
+699 -20

No files matched your search

-12
View File
@@ -347,8 +347,6 @@ class CodeTest(unittest.TestCase):
newcode = code.replace(co_name="func") # Should not raise SystemError
self.assertEqual(code, newcode)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_empty_linetable(self):
def func():
pass
@@ -468,8 +466,6 @@ class CodeTest(unittest.TestCase):
# co_positions behavior when info is missing.
# TODO: RUSTPYTHON
@unittest.expectedFailure
# @requires_debug_ranges()
def test_co_positions_empty_linetable(self):
def func():
@@ -480,8 +476,6 @@ class CodeTest(unittest.TestCase):
self.assertIsNone(line)
self.assertEqual(end_line, new_code.co_firstlineno + 1)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_code_equality(self):
def f():
try:
@@ -522,8 +516,6 @@ class CodeTest(unittest.TestCase):
self.assertNotEqual(c, swapped)
self.assertNotEqual(hash(c), hash(swapped))
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_code_hash_uses_bytecode(self):
c = (lambda x, y: x + y).__code__
d = (lambda x, y: x * y).__code__
@@ -735,8 +727,6 @@ class CodeLocationTest(unittest.TestCase):
self.assertEqual(l1, l2)
self.assertEqual(len(pos1), len(pos2))
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_positions(self):
self.check_positions(parse_location_table)
self.check_positions(misshappen)
@@ -751,8 +741,6 @@ class CodeLocationTest(unittest.TestCase):
self.assertEqual(l1, l2)
self.assertEqual(len(lines1), len(lines2))
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_lines(self):
self.check_lines(parse_location_table)
self.check_lines(misshappen)
-2
View File
@@ -885,8 +885,6 @@ if 1:
self.assertIn('LOAD_ATTR', instructions)
self.assertIn('PRECALL', instructions)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_lineno_procedure_call(self):
def call():
(
+138 -1
View File
@@ -5,7 +5,7 @@ use rustpython_compiler_core::{
OneIndexed, SourceLocation,
bytecode::{
CodeFlags, CodeObject, CodeUnit, ConstantData, InstrDisplayContext, Instruction, Label,
OpArg,
OpArg, PyCodeLocationInfoKind,
},
};
@@ -72,6 +72,7 @@ pub struct InstructionInfo {
pub target: BlockIdx,
// pub range: TextRange,
pub location: SourceLocation,
// TODO: end_location for debug ranges
}
// spell-checker:ignore petgraph
@@ -199,6 +200,9 @@ impl CodeInfo {
locations.clear()
}
// Generate linetable from locations
let linetable = generate_linetable(&locations, first_line_number.get() as i32);
Ok(CodeObject {
flags,
posonlyarg_count,
@@ -218,6 +222,8 @@ impl CodeInfo {
cellvars: cellvar_cache.into_iter().collect(),
freevars: freevar_cache.into_iter().collect(),
cell2arg,
linetable,
exceptiontable: Box::new([]), // TODO: Generate actual exception table
})
}
@@ -388,3 +394,134 @@ fn iter_blocks(blocks: &[Block]) -> impl Iterator<Item = (BlockIdx, &Block)> + '
Some((idx, b))
})
}
/// Generate CPython 3.11+ format linetable from source locations
fn generate_linetable(locations: &[SourceLocation], first_line: i32) -> Box<[u8]> {
if locations.is_empty() {
return Box::new([]);
}
let mut linetable = Vec::new();
// Initialize prev_line to first_line
// The first entry's delta is relative to co_firstlineno
let mut prev_line = first_line;
let mut i = 0;
while i < locations.len() {
let loc = &locations[i];
// Count consecutive instructions with the same location
let mut length = 1;
while i + length < locations.len() && locations[i + length] == locations[i] {
length += 1;
}
// Process in chunks of up to 8 instructions
while length > 0 {
let entry_length = length.min(8);
// Get line and column information
// SourceLocation always has row and column (both are OneIndexed)
let line = loc.row.get() as i32;
let col = (loc.column.get() as i32) - 1; // Convert 1-based to 0-based
let line_delta = line - prev_line;
// Choose the appropriate encoding based on line delta and column info
// Note: SourceLocation always has valid column, so we never get NO_COLUMNS case
if line_delta == 0 {
let end_col = col; // Use same column for end (no range info available)
if col < 80 && end_col - col < 16 && end_col >= col {
// Short form (codes 0-9) for common cases
let code = (col / 8).min(9) as u8; // Short0 to Short9
linetable.push(0x80 | (code << 3) | ((entry_length - 1) as u8));
let col_byte = (((col % 8) as u8) << 4) | ((end_col - col) as u8 & 0xf);
linetable.push(col_byte);
} else if col < 128 && end_col < 128 {
// One-line form (code 10) for same line
linetable.push(
0x80 | ((PyCodeLocationInfoKind::OneLine0 as u8) << 3)
| ((entry_length - 1) as u8),
);
linetable.push(col as u8);
linetable.push(end_col as u8);
} else {
// Long form for columns >= 128
linetable.push(
0x80 | ((PyCodeLocationInfoKind::Long as u8) << 3)
| ((entry_length - 1) as u8),
);
write_signed_varint(&mut linetable, 0); // line_delta = 0
write_varint(&mut linetable, 0); // end_line delta = 0
write_varint(&mut linetable, (col as u32) + 1); // column + 1 for encoding
write_varint(&mut linetable, (end_col as u32) + 1); // end_col + 1
}
} else if line_delta > 0 && line_delta < 3
/* && column.is_some() */
{
// One-line form (codes 11-12) for line deltas 1-2
let end_col = col; // Use same column for end
if col < 128 && end_col < 128 {
let code = (PyCodeLocationInfoKind::OneLine0 as u8) + (line_delta as u8); // 11 for delta=1, 12 for delta=2
linetable.push(0x80 | (code << 3) | ((entry_length - 1) as u8));
linetable.push(col as u8);
linetable.push(end_col as u8);
} else {
// Long form for columns >= 128 or negative line delta
linetable.push(
0x80 | ((PyCodeLocationInfoKind::Long as u8) << 3)
| ((entry_length - 1) as u8),
);
write_signed_varint(&mut linetable, line_delta);
write_varint(&mut linetable, 0); // end_line delta = 0
write_varint(&mut linetable, (col as u32) + 1); // column + 1 for encoding
write_varint(&mut linetable, (end_col as u32) + 1); // end_col + 1
}
} else {
// Long form (code 14) for all other cases
// This handles: line_delta < 0, line_delta >= 3, or columns >= 128
let end_col = col; // Use same column for end
linetable.push(
0x80 | ((PyCodeLocationInfoKind::Long as u8) << 3) | ((entry_length - 1) as u8),
);
write_signed_varint(&mut linetable, line_delta);
write_varint(&mut linetable, 0); // end_line delta = 0
write_varint(&mut linetable, (col as u32) + 1); // column + 1 for encoding
write_varint(&mut linetable, (end_col as u32) + 1); // end_col + 1
}
prev_line = line;
length -= entry_length;
i += entry_length;
}
}
linetable.into_boxed_slice()
}
/// Write a variable-length unsigned integer (6-bit chunks)
/// Returns the number of bytes written
fn write_varint(buf: &mut Vec<u8>, mut val: u32) -> usize {
let start_len = buf.len();
while val >= 64 {
buf.push(0x40 | (val & 0x3f) as u8);
val >>= 6;
}
buf.push(val as u8);
buf.len() - start_len
}
/// Write a variable-length signed integer
/// Returns the number of bytes written
fn write_signed_varint(buf: &mut Vec<u8>, val: i32) -> usize {
let uval = if val < 0 {
// (unsigned int)(-val) has an undefined behavior for INT_MIN
// So we use (0 - val as u32) to handle it correctly
((0u32.wrapping_sub(val as u32)) << 1) | 1
} else {
(val as u32) << 1
};
write_varint(buf, uval)
}
+77
View File
@@ -33,6 +33,75 @@ pub enum ResumeType {
AfterAwait = 3,
}
/// CPython 3.11+ linetable location info codes
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum PyCodeLocationInfoKind {
// Short forms are 0 to 9
Short0 = 0,
Short1 = 1,
Short2 = 2,
Short3 = 3,
Short4 = 4,
Short5 = 5,
Short6 = 6,
Short7 = 7,
Short8 = 8,
Short9 = 9,
// One line forms are 10 to 12
OneLine0 = 10,
OneLine1 = 11,
OneLine2 = 12,
NoColumns = 13,
Long = 14,
None = 15,
}
impl PyCodeLocationInfoKind {
pub fn from_code(code: u8) -> Option<Self> {
match code {
0 => Some(Self::Short0),
1 => Some(Self::Short1),
2 => Some(Self::Short2),
3 => Some(Self::Short3),
4 => Some(Self::Short4),
5 => Some(Self::Short5),
6 => Some(Self::Short6),
7 => Some(Self::Short7),
8 => Some(Self::Short8),
9 => Some(Self::Short9),
10 => Some(Self::OneLine0),
11 => Some(Self::OneLine1),
12 => Some(Self::OneLine2),
13 => Some(Self::NoColumns),
14 => Some(Self::Long),
15 => Some(Self::None),
_ => Option::None,
}
}
pub fn is_short(&self) -> bool {
(*self as u8) <= 9
}
pub fn short_column_group(&self) -> Option<u8> {
if self.is_short() {
Some(*self as u8)
} else {
Option::None
}
}
pub fn one_line_delta(&self) -> Option<i32> {
match self {
Self::OneLine0 => Some(0),
Self::OneLine1 => Some(1),
Self::OneLine2 => Some(2),
_ => Option::None,
}
}
}
pub trait Constant: Sized {
type Name: AsRef<str>;
@@ -146,6 +215,10 @@ pub struct CodeObject<C: Constant = ConstantData> {
pub varnames: Box<[C::Name]>,
pub cellvars: Box<[C::Name]>,
pub freevars: Box<[C::Name]>,
pub linetable: Box<[u8]>,
// Line number table (CPython 3.11+ format)
pub exceptiontable: Box<[u8]>,
// Exception handling table
}
bitflags! {
@@ -1202,6 +1275,8 @@ impl<C: Constant> CodeObject<C> {
first_line_number: self.first_line_number,
max_stackdepth: self.max_stackdepth,
cell2arg: self.cell2arg,
linetable: self.linetable,
exceptiontable: self.exceptiontable,
}
}
@@ -1232,6 +1307,8 @@ impl<C: Constant> CodeObject<C> {
first_line_number: self.first_line_number,
max_stackdepth: self.max_stackdepth,
cell2arg: self.cell2arg.clone(),
linetable: self.linetable.clone(),
exceptiontable: self.exceptiontable.clone(),
}
}
}
+16
View File
@@ -251,6 +251,16 @@ pub fn deserialize_code<R: Read, Bag: ConstantBag>(
let cellvars = read_names()?;
let freevars = read_names()?;
// Read linetable and exceptiontable
let linetable_len = rdr.read_u32()?;
let linetable = rdr.read_slice(linetable_len)?.to_vec().into_boxed_slice();
let exceptiontable_len = rdr.read_u32()?;
let exceptiontable = rdr
.read_slice(exceptiontable_len)?
.to_vec()
.into_boxed_slice();
Ok(CodeObject {
instructions,
locations,
@@ -269,6 +279,8 @@ pub fn deserialize_code<R: Read, Bag: ConstantBag>(
varnames,
cellvars,
freevars,
linetable,
exceptiontable,
})
}
@@ -684,4 +696,8 @@ pub fn serialize_code<W: Write, C: Constant>(buf: &mut W, code: &CodeObject<C>)
write_names(&code.varnames);
write_names(&code.cellvars);
write_names(&code.freevars);
// Serialize linetable and exceptiontable
write_vec(buf, &code.linetable);
write_vec(buf, &code.exceptiontable);
}
+468 -5
View File
@@ -16,8 +16,125 @@ use crate::{
use malachite_bigint::BigInt;
use num_traits::Zero;
use rustpython_compiler_core::OneIndexed;
use rustpython_compiler_core::bytecode::PyCodeLocationInfoKind;
use std::{borrow::Borrow, fmt, ops::Deref};
/// State for iterating through code address ranges
struct PyCodeAddressRange<'a> {
ar_start: i32,
ar_end: i32,
ar_line: i32,
computed_line: i32,
reader: LineTableReader<'a>,
}
impl<'a> PyCodeAddressRange<'a> {
fn new(linetable: &'a [u8], first_line: i32) -> Self {
PyCodeAddressRange {
ar_start: 0,
ar_end: 0,
ar_line: -1,
computed_line: first_line,
reader: LineTableReader::new(linetable),
}
}
/// Check if this is a NO_LINE marker (code 15)
fn is_no_line_marker(byte: u8) -> bool {
(byte >> 3) == 0x1f
}
/// Advance to next address range
fn advance(&mut self) -> bool {
if self.reader.at_end() {
return false;
}
let first_byte = match self.reader.read_byte() {
Some(b) => b,
None => return false,
};
if (first_byte & 0x80) == 0 {
return false; // Invalid linetable
}
let code = (first_byte >> 3) & 0x0f;
let length = ((first_byte & 0x07) + 1) as i32;
// Get line delta for this entry
let line_delta = self.get_line_delta(code);
// Update computed line
self.computed_line += line_delta;
// Check for NO_LINE marker
if Self::is_no_line_marker(first_byte) {
self.ar_line = -1;
} else {
self.ar_line = self.computed_line;
}
// Update address range
self.ar_start = self.ar_end;
self.ar_end += length * 2; // sizeof(_Py_CODEUNIT) = 2
// Skip remaining bytes for this entry
while !self.reader.at_end() {
if let Some(b) = self.reader.peek_byte() {
if (b & 0x80) != 0 {
break;
}
self.reader.read_byte();
} else {
break;
}
}
true
}
fn get_line_delta(&mut self, code: u8) -> i32 {
let kind = match PyCodeLocationInfoKind::from_code(code) {
Some(k) => k,
None => return 0,
};
match kind {
PyCodeLocationInfoKind::None => 0, // NO_LINE marker
PyCodeLocationInfoKind::Long => {
let delta = self.reader.read_signed_varint();
// Skip end_line, col, end_col
self.reader.read_varint();
self.reader.read_varint();
self.reader.read_varint();
delta
}
PyCodeLocationInfoKind::NoColumns => self.reader.read_signed_varint(),
PyCodeLocationInfoKind::OneLine0 => {
self.reader.read_byte(); // Skip column
self.reader.read_byte(); // Skip end column
0
}
PyCodeLocationInfoKind::OneLine1 => {
self.reader.read_byte(); // Skip column
self.reader.read_byte(); // Skip end column
1
}
PyCodeLocationInfoKind::OneLine2 => {
self.reader.read_byte(); // Skip column
self.reader.read_byte(); // Skip end column
2
}
_ if kind.is_short() => {
self.reader.read_byte(); // Skip column byte
0
}
_ => 0,
}
}
}
#[derive(FromArgs)]
pub struct ReplaceArgs {
#[pyarg(named, optional)]
@@ -40,6 +157,22 @@ pub struct ReplaceArgs {
co_flags: OptionalArg<u16>,
#[pyarg(named, optional)]
co_varnames: OptionalArg<Vec<PyObjectRef>>,
#[pyarg(named, optional)]
co_nlocals: OptionalArg<u32>,
#[pyarg(named, optional)]
co_stacksize: OptionalArg<u32>,
#[pyarg(named, optional)]
co_code: OptionalArg<crate::builtins::PyBytesRef>,
#[pyarg(named, optional)]
co_linetable: OptionalArg<crate::builtins::PyBytesRef>,
#[pyarg(named, optional)]
co_exceptiontable: OptionalArg<crate::builtins::PyBytesRef>,
#[pyarg(named, optional)]
co_freevars: OptionalArg<Vec<PyObjectRef>>,
#[pyarg(named, optional)]
co_cellvars: OptionalArg<Vec<PyObjectRef>>,
#[pyarg(named, optional)]
co_qualname: OptionalArg<PyStrRef>,
}
#[derive(Clone)]
@@ -350,6 +483,211 @@ impl PyCode {
vm.ctx.new_tuple(names)
}
#[pygetset]
pub fn co_linetable(&self, vm: &VirtualMachine) -> crate::builtins::PyBytesRef {
// Return the actual linetable from the code object
vm.ctx.new_bytes(self.code.linetable.to_vec())
}
#[pygetset]
pub fn co_exceptiontable(&self, vm: &VirtualMachine) -> crate::builtins::PyBytesRef {
// Return the actual exception table from the code object
vm.ctx.new_bytes(self.code.exceptiontable.to_vec())
}
#[pymethod]
pub fn co_lines(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
// TODO: Implement lazy iterator (lineiterator) like CPython for better performance
// Currently returns eager list for simplicity
// Return an iterator over (start_offset, end_offset, lineno) tuples
let linetable = self.code.linetable.as_ref();
let mut lines = Vec::new();
if !linetable.is_empty() {
let first_line = self.code.first_line_number.map_or(0, |n| n.get() as i32);
let mut range = PyCodeAddressRange::new(linetable, first_line);
// Process all address ranges and merge consecutive entries with same line
let mut pending_entry: Option<(i32, i32, i32)> = None;
while range.advance() {
let start = range.ar_start;
let end = range.ar_end;
let line = range.ar_line;
if let Some((prev_start, _, prev_line)) = pending_entry {
if prev_line == line {
// Same line, extend the range
pending_entry = Some((prev_start, end, prev_line));
} else {
// Different line, emit the previous entry
let tuple = if prev_line == -1 {
vm.ctx.new_tuple(vec![
vm.ctx.new_int(prev_start).into(),
vm.ctx.new_int(start).into(),
vm.ctx.none(),
])
} else {
vm.ctx.new_tuple(vec![
vm.ctx.new_int(prev_start).into(),
vm.ctx.new_int(start).into(),
vm.ctx.new_int(prev_line).into(),
])
};
lines.push(tuple.into());
pending_entry = Some((start, end, line));
}
} else {
// First entry
pending_entry = Some((start, end, line));
}
}
// Emit the last pending entry
if let Some((start, end, line)) = pending_entry {
let tuple = if line == -1 {
vm.ctx.new_tuple(vec![
vm.ctx.new_int(start).into(),
vm.ctx.new_int(end).into(),
vm.ctx.none(),
])
} else {
vm.ctx.new_tuple(vec![
vm.ctx.new_int(start).into(),
vm.ctx.new_int(end).into(),
vm.ctx.new_int(line).into(),
])
};
lines.push(tuple.into());
}
}
let list = vm.ctx.new_list(lines);
vm.call_method(list.as_object(), "__iter__", ())
}
#[pymethod]
pub fn co_positions(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
// Return an iterator over (line, end_line, column, end_column) tuples for each instruction
let linetable = self.code.linetable.as_ref();
let mut positions = Vec::new();
if !linetable.is_empty() {
let mut reader = LineTableReader::new(linetable);
let mut line = self.code.first_line_number.map_or(0, |n| n.get() as i32);
while !reader.at_end() {
let first_byte = match reader.read_byte() {
Some(b) => b,
None => break,
};
if (first_byte & 0x80) == 0 {
break; // Invalid linetable
}
let code = (first_byte >> 3) & 0x0f;
let length = ((first_byte & 0x07) + 1) as i32;
let kind = match PyCodeLocationInfoKind::from_code(code) {
Some(k) => k,
None => break, // Invalid code
};
let (line_delta, end_line_delta, column, end_column): (
i32,
i32,
Option<i32>,
Option<i32>,
) = match kind {
PyCodeLocationInfoKind::None => {
// No location - all values are None
(0, 0, None, None)
}
PyCodeLocationInfoKind::Long => {
// Long form
let delta = reader.read_signed_varint();
let end_line_delta = reader.read_varint() as i32;
let col = reader.read_varint();
let column = if col == 0 {
None
} else {
Some((col - 1) as i32)
};
let end_col = reader.read_varint();
let end_column = if end_col == 0 {
None
} else {
Some((end_col - 1) as i32)
};
// endline = line + end_line_delta (will be computed after line update)
(delta, end_line_delta, column, end_column)
}
PyCodeLocationInfoKind::NoColumns => {
// No column form
let delta = reader.read_signed_varint();
(delta, 0, None, None) // endline will be same as line (delta = 0)
}
PyCodeLocationInfoKind::OneLine0
| PyCodeLocationInfoKind::OneLine1
| PyCodeLocationInfoKind::OneLine2 => {
// One-line form - endline = line
let col = reader.read_byte().unwrap_or(0) as i32;
let end_col = reader.read_byte().unwrap_or(0) as i32;
let delta = kind.one_line_delta().unwrap_or(0);
(delta, 0, Some(col), Some(end_col)) // endline = line (delta = 0)
}
_ if kind.is_short() => {
// Short form - endline = line
let col_data = reader.read_byte().unwrap_or(0);
let col_group = kind.short_column_group().unwrap_or(0);
let col = ((col_group as i32) << 3) | ((col_data >> 4) as i32);
let end_col = col + (col_data & 0x0f) as i32;
(0, 0, Some(col), Some(end_col)) // endline = line (delta = 0)
}
_ => (0, 0, None, None),
};
// Update line number
line += line_delta;
// Generate position tuples for each instruction covered by this entry
for _ in 0..length {
// Handle special case for no location (code 15)
let final_line = if kind == PyCodeLocationInfoKind::None {
None
} else {
Some(line)
};
let final_endline = if kind == PyCodeLocationInfoKind::None {
None
} else {
Some(line + end_line_delta)
};
// Convert Option to PyObject (None or int)
let line_obj = final_line.to_pyobject(vm);
let end_line_obj = final_endline.to_pyobject(vm);
let column_obj = column.to_pyobject(vm);
let end_column_obj = end_column.to_pyobject(vm);
let tuple =
vm.ctx
.new_tuple(vec![line_obj, end_line_obj, column_obj, end_column_obj]);
positions.push(tuple.into());
}
}
}
let list = vm.ctx.new_list(positions);
vm.call_method(list.as_object(), "__iter__", ())
}
#[pymethod]
pub fn replace(&self, args: ReplaceArgs, vm: &VirtualMachine) -> PyResult<Self> {
let posonlyarg_count = match args.co_posonlyargcount {
@@ -408,6 +746,66 @@ impl PyCode {
OptionalArg::Missing => self.code.varnames.iter().map(|s| s.to_object()).collect(),
};
let qualname = match args.co_qualname {
OptionalArg::Present(qualname) => qualname,
OptionalArg::Missing => self.code.qualname.to_owned(),
};
let max_stackdepth = match args.co_stacksize {
OptionalArg::Present(stacksize) => stacksize,
OptionalArg::Missing => self.code.max_stackdepth,
};
let instructions = match args.co_code {
OptionalArg::Present(_code_bytes) => {
// Convert bytes back to instructions
// For now, keep the original instructions
// TODO: Properly parse bytecode from bytes
self.code.instructions.clone()
}
OptionalArg::Missing => self.code.instructions.clone(),
};
let cellvars = match args.co_cellvars {
OptionalArg::Present(cellvars) => cellvars
.into_iter()
.map(|o| o.as_interned_str(vm).unwrap())
.collect(),
OptionalArg::Missing => self.code.cellvars.clone(),
};
let freevars = match args.co_freevars {
OptionalArg::Present(freevars) => freevars
.into_iter()
.map(|o| o.as_interned_str(vm).unwrap())
.collect(),
OptionalArg::Missing => self.code.freevars.clone(),
};
// Validate co_nlocals if provided
if let OptionalArg::Present(nlocals) = args.co_nlocals
&& nlocals as usize != varnames.len()
{
return Err(vm.new_value_error(format!(
"co_nlocals ({}) != len(co_varnames) ({})",
nlocals,
varnames.len()
)));
}
// Handle linetable and exceptiontable
let linetable = match args.co_linetable {
OptionalArg::Present(linetable) => linetable.as_bytes().to_vec().into_boxed_slice(),
OptionalArg::Missing => self.code.linetable.clone(),
};
let exceptiontable = match args.co_exceptiontable {
OptionalArg::Present(exceptiontable) => {
exceptiontable.as_bytes().to_vec().into_boxed_slice()
}
OptionalArg::Missing => self.code.exceptiontable.clone(),
};
Ok(Self {
code: CodeObject {
flags: CodeFlags::from_bits_truncate(flags),
@@ -417,10 +815,10 @@ impl PyCode {
source_path: source_path.as_object().as_interned_str(vm).unwrap(),
first_line_number,
obj_name: obj_name.as_object().as_interned_str(vm).unwrap(),
qualname: self.code.qualname,
qualname: qualname.as_object().as_interned_str(vm).unwrap(),
max_stackdepth: self.code.max_stackdepth,
instructions: self.code.instructions.clone(),
max_stackdepth,
instructions,
locations: self.code.locations.clone(),
constants: constants.into_iter().map(Literal).collect(),
names: names
@@ -431,9 +829,11 @@ impl PyCode {
.into_iter()
.map(|o| o.as_interned_str(vm).unwrap())
.collect(),
cellvars: self.code.cellvars.clone(),
freevars: self.code.freevars.clone(),
cellvars,
freevars,
cell2arg: self.code.cell2arg.clone(),
linetable,
exceptiontable,
},
})
}
@@ -457,6 +857,69 @@ impl ToPyObject for bytecode::CodeObject {
}
}
// Helper struct for reading linetable
struct LineTableReader<'a> {
data: &'a [u8],
pos: usize,
}
impl<'a> LineTableReader<'a> {
fn new(data: &'a [u8]) -> Self {
Self { data, pos: 0 }
}
fn read_byte(&mut self) -> Option<u8> {
if self.pos < self.data.len() {
let byte = self.data[self.pos];
self.pos += 1;
Some(byte)
} else {
None
}
}
fn peek_byte(&self) -> Option<u8> {
if self.pos < self.data.len() {
Some(self.data[self.pos])
} else {
None
}
}
fn read_varint(&mut self) -> u32 {
if let Some(first) = self.read_byte() {
let mut val = (first & 0x3f) as u32;
let mut shift = 0;
let mut byte = first;
while (byte & 0x40) != 0 {
if let Some(next) = self.read_byte() {
shift += 6;
val |= ((next & 0x3f) as u32) << shift;
byte = next;
} else {
break;
}
}
val
} else {
0
}
}
fn read_signed_varint(&mut self) -> i32 {
let uval = self.read_varint();
if uval & 1 != 0 {
-((uval >> 1) as i32)
} else {
(uval >> 1) as i32
}
}
fn at_end(&self) -> bool {
self.pos >= self.data.len()
}
}
pub fn init(ctx: &Context) {
PyCode::extend_class(ctx, ctx.types.code_type);
}