@@ -5,7 +5,7 @@ use rustpython_compiler_core::{
55 OneIndexed , SourceLocation ,
66 bytecode:: {
77 CodeFlags , CodeObject , CodeUnit , ConstantData , InstrDisplayContext , Instruction , Label ,
8- OpArg ,
8+ OpArg , PyCodeLocationInfoKind ,
99 } ,
1010} ;
1111
@@ -72,6 +72,7 @@ pub struct InstructionInfo {
7272 pub target : BlockIdx ,
7373 // pub range: TextRange,
7474 pub location : SourceLocation ,
75+ // TODO: end_location for debug ranges
7576}
7677
7778// spell-checker:ignore petgraph
@@ -199,6 +200,9 @@ impl CodeInfo {
199200 locations. clear ( )
200201 }
201202
203+ // Generate linetable from locations
204+ let linetable = generate_linetable ( & locations, first_line_number. get ( ) as i32 ) ;
205+
202206 Ok ( CodeObject {
203207 flags,
204208 posonlyarg_count,
@@ -218,6 +222,8 @@ impl CodeInfo {
218222 cellvars : cellvar_cache. into_iter ( ) . collect ( ) ,
219223 freevars : freevar_cache. into_iter ( ) . collect ( ) ,
220224 cell2arg,
225+ linetable,
226+ exceptiontable : Box :: new ( [ ] ) , // TODO: Generate actual exception table
221227 } )
222228 }
223229
@@ -388,3 +394,136 @@ fn iter_blocks(blocks: &[Block]) -> impl Iterator<Item = (BlockIdx, &Block)> + '
388394 Some ( ( idx, b) )
389395 } )
390396}
397+
398+ /// Generate CPython 3.11+ format linetable from source locations
399+ fn generate_linetable ( locations : & [ SourceLocation ] , first_line : i32 ) -> Box < [ u8 ] > {
400+ if locations. is_empty ( ) {
401+ return Box :: new ( [ ] ) ;
402+ }
403+
404+ let mut linetable = Vec :: new ( ) ;
405+ // Initialize prev_line to first_line
406+ // The first entry's delta is relative to co_firstlineno
407+ let mut prev_line = first_line;
408+ let mut i = 0 ;
409+
410+ while i < locations. len ( ) {
411+ let loc = & locations[ i] ;
412+
413+ // Count consecutive instructions with the same location
414+ let mut length = 1 ;
415+ while i + length < locations. len ( ) && locations[ i + length] == locations[ i] {
416+ length += 1 ;
417+ }
418+
419+ // Process in chunks of up to 8 instructions
420+ while length > 0 {
421+ let entry_length = length. min ( 8 ) ;
422+
423+ // Get line and column information
424+ // SourceLocation always has row and column (both are OneIndexed)
425+ let line = loc. row . get ( ) as i32 ;
426+ let col = ( loc. column . get ( ) as i32 ) - 1 ; // Convert 1-based to 0-based
427+ let column = Some ( col) ;
428+
429+ let line_delta = line - prev_line;
430+
431+ // Choose the appropriate encoding based on line delta and column info
432+ // Note: SourceLocation always has valid column, so we never get NO_COLUMNS case
433+ if line_delta == 0 {
434+ let col = column. unwrap ( ) ;
435+ let end_col = col; // Use same column for end (no range info available)
436+
437+ if col < 80 && end_col - col < 16 && end_col >= col {
438+ // Short form (codes 0-9) for common cases
439+ let code = ( col / 8 ) . min ( 9 ) as u8 ; // Short0 to Short9
440+ linetable. push ( 0x80 | ( code << 3 ) | ( ( entry_length - 1 ) as u8 ) ) ;
441+ let col_byte = ( ( ( col % 8 ) as u8 ) << 4 ) | ( ( end_col - col) as u8 & 0xf ) ;
442+ linetable. push ( col_byte) ;
443+ } else if col < 128 && end_col < 128 {
444+ // One-line form (code 10) for same line
445+ linetable. push (
446+ 0x80 | ( ( PyCodeLocationInfoKind :: OneLine0 as u8 ) << 3 )
447+ | ( ( entry_length - 1 ) as u8 ) ,
448+ ) ;
449+ linetable. push ( col as u8 ) ;
450+ linetable. push ( end_col as u8 ) ;
451+ } else {
452+ // Long form for columns >= 128
453+ linetable. push (
454+ 0x80 | ( ( PyCodeLocationInfoKind :: Long as u8 ) << 3 )
455+ | ( ( entry_length - 1 ) as u8 ) ,
456+ ) ;
457+ write_signed_varint ( & mut linetable, 0 ) ; // line_delta = 0
458+ write_varint ( & mut linetable, 0 ) ; // end_line delta = 0
459+ write_varint ( & mut linetable, ( col as u32 ) + 1 ) ; // column + 1 for encoding
460+ write_varint ( & mut linetable, ( end_col as u32 ) + 1 ) ; // end_col + 1
461+ }
462+ } else if line_delta > 0 && line_delta < 3 && column. is_some ( ) {
463+ // One-line form (codes 11-12) for line deltas 1-2
464+ let col = column. unwrap ( ) ;
465+ let end_col = col; // Use same column for end
466+
467+ if col < 128 && end_col < 128 {
468+ let code = ( PyCodeLocationInfoKind :: OneLine0 as u8 ) + ( line_delta as u8 ) ; // 11 for delta=1, 12 for delta=2
469+ linetable. push ( 0x80 | ( code << 3 ) | ( ( entry_length - 1 ) as u8 ) ) ;
470+ linetable. push ( col as u8 ) ;
471+ linetable. push ( end_col as u8 ) ;
472+ } else {
473+ // Long form for columns >= 128 or negative line delta
474+ linetable. push (
475+ 0x80 | ( ( PyCodeLocationInfoKind :: Long as u8 ) << 3 )
476+ | ( ( entry_length - 1 ) as u8 ) ,
477+ ) ;
478+ write_signed_varint ( & mut linetable, line_delta) ;
479+ write_varint ( & mut linetable, 0 ) ; // end_line delta = 0
480+ write_varint ( & mut linetable, ( col as u32 ) + 1 ) ; // column + 1 for encoding
481+ write_varint ( & mut linetable, ( end_col as u32 ) + 1 ) ; // end_col + 1
482+ }
483+ } else {
484+ // Long form (code 14) for all other cases
485+ // This handles: line_delta < 0, line_delta >= 3, or columns >= 128
486+ let col = column. unwrap_or ( 0 ) ;
487+ let end_col = col; // Use same column for end
488+ linetable. push (
489+ 0x80 | ( ( PyCodeLocationInfoKind :: Long as u8 ) << 3 ) | ( ( entry_length - 1 ) as u8 ) ,
490+ ) ;
491+ write_signed_varint ( & mut linetable, line_delta) ;
492+ write_varint ( & mut linetable, 0 ) ; // end_line delta = 0
493+ write_varint ( & mut linetable, ( col as u32 ) + 1 ) ; // column + 1 for encoding
494+ write_varint ( & mut linetable, ( end_col as u32 ) + 1 ) ; // end_col + 1
495+ }
496+
497+ prev_line = line;
498+ length -= entry_length;
499+ i += entry_length;
500+ }
501+ }
502+
503+ linetable. into_boxed_slice ( )
504+ }
505+
506+ /// Write a variable-length unsigned integer (6-bit chunks)
507+ /// Returns the number of bytes written
508+ fn write_varint ( buf : & mut Vec < u8 > , mut val : u32 ) -> usize {
509+ let start_len = buf. len ( ) ;
510+ while val >= 64 {
511+ buf. push ( 0x40 | ( val & 0x3f ) as u8 ) ;
512+ val >>= 6 ;
513+ }
514+ buf. push ( val as u8 ) ;
515+ buf. len ( ) - start_len
516+ }
517+
518+ /// Write a variable-length signed integer
519+ /// Returns the number of bytes written
520+ fn write_signed_varint ( buf : & mut Vec < u8 > , val : i32 ) -> usize {
521+ let uval = if val < 0 {
522+ // (unsigned int)(-val) has an undefined behavior for INT_MIN
523+ // So we use (0 - val as u32) to handle it correctly
524+ ( ( 0u32 . wrapping_sub ( val as u32 ) ) << 1 ) | 1
525+ } else {
526+ ( val as u32 ) << 1
527+ } ;
528+ write_varint ( buf, uval)
529+ }
0 commit comments