@@ -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,134 @@ 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+
428+ let line_delta = line - prev_line;
429+
430+ // Choose the appropriate encoding based on line delta and column info
431+ // Note: SourceLocation always has valid column, so we never get NO_COLUMNS case
432+ if line_delta == 0 {
433+ let end_col = col; // Use same column for end (no range info available)
434+
435+ if col < 80 && end_col - col < 16 && end_col >= col {
436+ // Short form (codes 0-9) for common cases
437+ let code = ( col / 8 ) . min ( 9 ) as u8 ; // Short0 to Short9
438+ linetable. push ( 0x80 | ( code << 3 ) | ( ( entry_length - 1 ) as u8 ) ) ;
439+ let col_byte = ( ( ( col % 8 ) as u8 ) << 4 ) | ( ( end_col - col) as u8 & 0xf ) ;
440+ linetable. push ( col_byte) ;
441+ } else if col < 128 && end_col < 128 {
442+ // One-line form (code 10) for same line
443+ linetable. push (
444+ 0x80 | ( ( PyCodeLocationInfoKind :: OneLine0 as u8 ) << 3 )
445+ | ( ( entry_length - 1 ) as u8 ) ,
446+ ) ;
447+ linetable. push ( col as u8 ) ;
448+ linetable. push ( end_col as u8 ) ;
449+ } else {
450+ // Long form for columns >= 128
451+ linetable. push (
452+ 0x80 | ( ( PyCodeLocationInfoKind :: Long as u8 ) << 3 )
453+ | ( ( entry_length - 1 ) as u8 ) ,
454+ ) ;
455+ write_signed_varint ( & mut linetable, 0 ) ; // line_delta = 0
456+ write_varint ( & mut linetable, 0 ) ; // end_line delta = 0
457+ write_varint ( & mut linetable, ( col as u32 ) + 1 ) ; // column + 1 for encoding
458+ write_varint ( & mut linetable, ( end_col as u32 ) + 1 ) ; // end_col + 1
459+ }
460+ } else if line_delta > 0 && line_delta < 3
461+ /* && column.is_some() */
462+ {
463+ // One-line form (codes 11-12) for line deltas 1-2
464+ let end_col = col; // Use same column for end
465+
466+ if col < 128 && end_col < 128 {
467+ let code = ( PyCodeLocationInfoKind :: OneLine0 as u8 ) + ( line_delta as u8 ) ; // 11 for delta=1, 12 for delta=2
468+ linetable. push ( 0x80 | ( code << 3 ) | ( ( entry_length - 1 ) as u8 ) ) ;
469+ linetable. push ( col as u8 ) ;
470+ linetable. push ( end_col as u8 ) ;
471+ } else {
472+ // Long form for columns >= 128 or negative line delta
473+ linetable. push (
474+ 0x80 | ( ( PyCodeLocationInfoKind :: Long as u8 ) << 3 )
475+ | ( ( entry_length - 1 ) as u8 ) ,
476+ ) ;
477+ write_signed_varint ( & mut linetable, line_delta) ;
478+ write_varint ( & mut linetable, 0 ) ; // end_line delta = 0
479+ write_varint ( & mut linetable, ( col as u32 ) + 1 ) ; // column + 1 for encoding
480+ write_varint ( & mut linetable, ( end_col as u32 ) + 1 ) ; // end_col + 1
481+ }
482+ } else {
483+ // Long form (code 14) for all other cases
484+ // This handles: line_delta < 0, line_delta >= 3, or columns >= 128
485+ let end_col = col; // Use same column for end
486+ linetable. push (
487+ 0x80 | ( ( PyCodeLocationInfoKind :: Long as u8 ) << 3 ) | ( ( entry_length - 1 ) as u8 ) ,
488+ ) ;
489+ write_signed_varint ( & mut linetable, line_delta) ;
490+ write_varint ( & mut linetable, 0 ) ; // end_line delta = 0
491+ write_varint ( & mut linetable, ( col as u32 ) + 1 ) ; // column + 1 for encoding
492+ write_varint ( & mut linetable, ( end_col as u32 ) + 1 ) ; // end_col + 1
493+ }
494+
495+ prev_line = line;
496+ length -= entry_length;
497+ i += entry_length;
498+ }
499+ }
500+
501+ linetable. into_boxed_slice ( )
502+ }
503+
504+ /// Write a variable-length unsigned integer (6-bit chunks)
505+ /// Returns the number of bytes written
506+ fn write_varint ( buf : & mut Vec < u8 > , mut val : u32 ) -> usize {
507+ let start_len = buf. len ( ) ;
508+ while val >= 64 {
509+ buf. push ( 0x40 | ( val & 0x3f ) as u8 ) ;
510+ val >>= 6 ;
511+ }
512+ buf. push ( val as u8 ) ;
513+ buf. len ( ) - start_len
514+ }
515+
516+ /// Write a variable-length signed integer
517+ /// Returns the number of bytes written
518+ fn write_signed_varint ( buf : & mut Vec < u8 > , val : i32 ) -> usize {
519+ let uval = if val < 0 {
520+ // (unsigned int)(-val) has an undefined behavior for INT_MIN
521+ // So we use (0 - val as u32) to handle it correctly
522+ ( ( 0u32 . wrapping_sub ( val as u32 ) ) << 1 ) | 1
523+ } else {
524+ ( val as u32 ) << 1
525+ } ;
526+ write_varint ( buf, uval)
527+ }
0 commit comments