@@ -125,7 +125,7 @@ pub struct ExceptHandlerInfo {
125125// spell-checker:ignore petgraph
126126// TODO: look into using petgraph for handling blocks and stuff? it's heavier than this, but it
127127// might enable more analysis/optimizations
128- #[ derive( Debug ) ]
128+ #[ derive( Debug , Clone ) ]
129129pub struct Block {
130130 pub instructions : Vec < InstructionInfo > ,
131131 pub next : BlockIdx ,
@@ -225,6 +225,8 @@ impl CodeInfo {
225225 normalize_jumps ( & mut self . blocks ) ;
226226 self . dce ( ) ; // re-run within-block DCE after normalize_jumps creates new instructions
227227 self . eliminate_unreachable_blocks ( ) ;
228+ // TODO: duplicate_exits_without_lineno disabled pending stackdepth fix
229+ // duplicate_exits_without_lineno(&mut self.blocks);
228230 duplicate_end_returns ( & mut self . blocks ) ;
229231 self . dce ( ) ; // truncate after terminal in blocks that got return duplicated
230232 self . eliminate_unreachable_blocks ( ) ; // remove now-unreachable last block
@@ -2239,15 +2241,14 @@ fn jump_threading(blocks: &mut [Block]) {
22392241 }
22402242 // Check if target block's first instruction is an unconditional jump
22412243 let target_block = & blocks[ target. idx ( ) ] ;
2242- if let Some ( target_ins) = target_block. instructions . first ( ) {
2243- if target_ins. instr . is_unconditional_jump ( )
2244- && target_ins. target != BlockIdx :: NULL
2245- && target_ins. target != target
2246- {
2247- let final_target = target_ins. target ;
2248- blocks[ bi] . instructions [ last_idx] . target = final_target;
2249- changed = true ;
2250- }
2244+ if let Some ( target_ins) = target_block. instructions . first ( )
2245+ && target_ins. instr . is_unconditional_jump ( )
2246+ && target_ins. target != BlockIdx :: NULL
2247+ && target_ins. target != target
2248+ {
2249+ let final_target = target_ins. target ;
2250+ blocks[ bi] . instructions [ last_idx] . target = final_target;
2251+ changed = true ;
22512252 }
22522253 }
22532254 }
@@ -2301,8 +2302,7 @@ fn normalize_jumps(blocks: &mut Vec<Block>) {
23012302
23022303 visited. fill ( false ) ;
23032304
2304- for vi in 0 ..visit_order. len ( ) {
2305- let block_idx = visit_order[ vi] ;
2305+ for & block_idx in & visit_order {
23062306 let idx = block_idx. idx ( ) ;
23072307 visited[ idx] = true ;
23082308
@@ -2323,76 +2323,80 @@ fn normalize_jumps(blocks: &mut Vec<Block>) {
23232323
23242324 // Normalize conditional jumps: forward gets NOT_TAKEN, backward gets inverted
23252325 let last = blocks[ idx] . instructions . last ( ) ;
2326- if let Some ( last_ins) = last {
2327- if is_conditional_jump ( & last_ins. instr ) && last_ins. target != BlockIdx :: NULL {
2328- let target = last_ins. target ;
2329- let is_forward = !visited[ target. idx ( ) ] ;
2330-
2331- if is_forward {
2332- // Insert NOT_TAKEN after forward conditional jump
2333- let not_taken = InstructionInfo {
2326+ if let Some ( last_ins) = last
2327+ && is_conditional_jump ( & last_ins. instr )
2328+ && last_ins. target != BlockIdx :: NULL
2329+ {
2330+ let target = last_ins. target ;
2331+ let is_forward = !visited[ target. idx ( ) ] ;
2332+
2333+ if is_forward {
2334+ // Insert NOT_TAKEN after forward conditional jump
2335+ let not_taken = InstructionInfo {
2336+ instr : Instruction :: NotTaken . into ( ) ,
2337+ arg : OpArg :: new ( 0 ) ,
2338+ target : BlockIdx :: NULL ,
2339+ location : last_ins. location ,
2340+ end_location : last_ins. end_location ,
2341+ except_handler : last_ins. except_handler ,
2342+ lineno_override : None ,
2343+ cache_entries : 0 ,
2344+ } ;
2345+ blocks[ idx] . instructions . push ( not_taken) ;
2346+ } else {
2347+ // Backward conditional jump: invert and create new block
2348+ // Transform: `cond_jump T` (backward)
2349+ // Into: `reversed_cond_jump b_next` + new block [NOT_TAKEN, JUMP T]
2350+ let loc = last_ins. location ;
2351+ let end_loc = last_ins. end_location ;
2352+ let exc_handler = last_ins. except_handler ;
2353+
2354+ if let Some ( reversed) = reversed_conditional ( & last_ins. instr ) {
2355+ let old_next = blocks[ idx] . next ;
2356+ let is_cold = blocks[ idx] . cold ;
2357+
2358+ // Create new block with NOT_TAKEN + JUMP to original backward target
2359+ let new_block_idx = BlockIdx ( blocks. len ( ) as u32 ) ;
2360+ let mut new_block = Block {
2361+ cold : is_cold,
2362+ ..Block :: default ( )
2363+ } ;
2364+ new_block. instructions . push ( InstructionInfo {
23342365 instr : Instruction :: NotTaken . into ( ) ,
23352366 arg : OpArg :: new ( 0 ) ,
23362367 target : BlockIdx :: NULL ,
2337- location : last_ins . location ,
2338- end_location : last_ins . end_location ,
2339- except_handler : last_ins . except_handler ,
2368+ location : loc ,
2369+ end_location : end_loc ,
2370+ except_handler : exc_handler ,
23402371 lineno_override : None ,
23412372 cache_entries : 0 ,
2342- } ;
2343- blocks[ idx] . instructions . push ( not_taken) ;
2344- } else {
2345- // Backward conditional jump: invert and create new block
2346- // Transform: `cond_jump T` (backward)
2347- // Into: `reversed_cond_jump b_next` + new block [NOT_TAKEN, JUMP T]
2348- let loc = last_ins. location ;
2349- let end_loc = last_ins. end_location ;
2350- let exc_handler = last_ins. except_handler ;
2351-
2352- if let Some ( reversed) = reversed_conditional ( & last_ins. instr ) {
2353- let old_next = blocks[ idx] . next ;
2354- let is_cold = blocks[ idx] . cold ;
2355-
2356- // Create new block with NOT_TAKEN + JUMP to original backward target
2357- let new_block_idx = BlockIdx ( blocks. len ( ) as u32 ) ;
2358- let mut new_block = Block {
2359- cold : is_cold,
2360- ..Block :: default ( )
2361- } ;
2362- new_block. instructions . push ( InstructionInfo {
2363- instr : Instruction :: NotTaken . into ( ) ,
2364- arg : OpArg :: new ( 0 ) ,
2365- target : BlockIdx :: NULL ,
2366- location : loc,
2367- end_location : end_loc,
2368- except_handler : exc_handler,
2369- lineno_override : None ,
2370- cache_entries : 0 ,
2371- } ) ;
2372- new_block. instructions . push ( InstructionInfo {
2373- instr : PseudoInstruction :: Jump { delta : Arg :: marker ( ) } . into ( ) ,
2374- arg : OpArg :: new ( 0 ) ,
2375- target,
2376- location : loc,
2377- end_location : end_loc,
2378- except_handler : exc_handler,
2379- lineno_override : None ,
2380- cache_entries : 0 ,
2381- } ) ;
2382- new_block. next = old_next;
2383-
2384- // Update the conditional jump: invert opcode, target = old next block
2385- let last_mut = blocks[ idx] . instructions . last_mut ( ) . unwrap ( ) ;
2386- last_mut. instr = reversed;
2387- last_mut. target = old_next;
2388-
2389- // Splice new block between current and old next
2390- blocks[ idx] . next = new_block_idx;
2391- blocks. push ( new_block) ;
2392-
2393- // Extend visited array and update visit order
2394- visited. push ( true ) ;
2395- }
2373+ } ) ;
2374+ new_block. instructions . push ( InstructionInfo {
2375+ instr : PseudoInstruction :: Jump {
2376+ delta : Arg :: marker ( ) ,
2377+ }
2378+ . into ( ) ,
2379+ arg : OpArg :: new ( 0 ) ,
2380+ target,
2381+ location : loc,
2382+ end_location : end_loc,
2383+ except_handler : exc_handler,
2384+ lineno_override : None ,
2385+ cache_entries : 0 ,
2386+ } ) ;
2387+ new_block. next = old_next;
2388+
2389+ // Update the conditional jump: invert opcode, target = old next block
2390+ let last_mut = blocks[ idx] . instructions . last_mut ( ) . unwrap ( ) ;
2391+ last_mut. instr = reversed;
2392+ last_mut. target = old_next;
2393+
2394+ // Splice new block between current and old next
2395+ blocks[ idx] . next = new_block_idx;
2396+ blocks. push ( new_block) ;
2397+
2398+ // Extend visited array and update visit order
2399+ visited. push ( true ) ;
23962400 }
23972401 }
23982402 }
@@ -2496,10 +2500,111 @@ fn normalize_jumps(blocks: &mut Vec<Block>) {
24962500 }
24972501}
24982502
2503+ #[ allow( dead_code) ]
2504+ fn is_simple_return_block ( block : & Block ) -> bool {
2505+ block. instructions . len ( ) == 1
2506+ && matches ! (
2507+ block. instructions[ 0 ] . instr. real( ) ,
2508+ Some ( Instruction :: ReturnValue )
2509+ )
2510+ }
2511+
2512+ /// Follow chain of empty blocks to find first non-empty block.
2513+ fn next_nonempty_block ( blocks : & [ Block ] , mut idx : BlockIdx ) -> BlockIdx {
2514+ while idx != BlockIdx :: NULL
2515+ && blocks[ idx. idx ( ) ] . instructions . is_empty ( )
2516+ && blocks[ idx. idx ( ) ] . next != BlockIdx :: NULL
2517+ {
2518+ idx = blocks[ idx. idx ( ) ] . next ;
2519+ }
2520+ idx
2521+ }
2522+
2523+ #[ allow( dead_code) ]
2524+ fn duplicate_exits_without_lineno ( blocks : & mut Vec < Block > ) {
2525+ // Count predecessors for each block
2526+ let mut predecessors = vec ! [ 0u32 ; blocks. len( ) ] ;
2527+ let mut current = BlockIdx ( 0 ) ;
2528+ while current != BlockIdx :: NULL {
2529+ let block = & blocks[ current. idx ( ) ] ;
2530+ // Fall-through predecessor
2531+ let next = next_nonempty_block ( blocks, block. next ) ;
2532+ if next != BlockIdx :: NULL {
2533+ let has_fallthrough = block
2534+ . instructions
2535+ . last ( )
2536+ . is_none_or ( |ins| !ins. instr . is_scope_exit ( ) && !ins. instr . is_unconditional_jump ( ) ) ;
2537+ if has_fallthrough {
2538+ predecessors[ next. idx ( ) ] += 1 ;
2539+ }
2540+ }
2541+ // Jump target predecessor
2542+ for ins in & block. instructions {
2543+ if ins. target != BlockIdx :: NULL {
2544+ let target = next_nonempty_block ( blocks, ins. target ) ;
2545+ if target != BlockIdx :: NULL {
2546+ predecessors[ target. idx ( ) ] += 1 ;
2547+ }
2548+ }
2549+ }
2550+ current = block. next ;
2551+ }
2552+
2553+ // For each block, if its last instruction jumps to an exit block without lineno
2554+ // that has >1 predecessor, copy that exit block.
2555+ current = BlockIdx ( 0 ) ;
2556+ while current != BlockIdx :: NULL {
2557+ let block = & blocks[ current. idx ( ) ] ;
2558+ let last = match block. instructions . last ( ) {
2559+ Some ( ins) if ins. target != BlockIdx :: NULL => ins,
2560+ _ => {
2561+ current = blocks[ current. idx ( ) ] . next ;
2562+ continue ;
2563+ }
2564+ } ;
2565+ if !last. instr . is_unconditional_jump ( ) && !is_conditional_jump ( & last. instr ) {
2566+ current = blocks[ current. idx ( ) ] . next ;
2567+ continue ;
2568+ }
2569+
2570+ let target = next_nonempty_block ( blocks, last. target ) ;
2571+ if target == BlockIdx :: NULL || !is_simple_return_block ( & blocks[ target. idx ( ) ] ) {
2572+ current = blocks[ current. idx ( ) ] . next ;
2573+ continue ;
2574+ }
2575+ if predecessors[ target. idx ( ) ] <= 1 {
2576+ current = blocks[ current. idx ( ) ] . next ;
2577+ continue ;
2578+ }
2579+
2580+ // Copy the exit block
2581+ let new_idx = BlockIdx ( blocks. len ( ) as u32 ) ;
2582+ let mut new_block = blocks[ target. idx ( ) ] . clone ( ) ;
2583+ // Set location from the jump instruction
2584+ let jump_loc = blocks[ current. idx ( ) ] . instructions . last ( ) . unwrap ( ) . location ;
2585+ let jump_end_loc = blocks[ current. idx ( ) ]
2586+ . instructions
2587+ . last ( )
2588+ . unwrap ( )
2589+ . end_location ;
2590+ if let Some ( first) = new_block. instructions . first_mut ( ) {
2591+ first. location = jump_loc;
2592+ first. end_location = jump_end_loc;
2593+ }
2594+ new_block. next = blocks[ target. idx ( ) ] . next ;
2595+ blocks. push ( new_block) ;
2596+
2597+ // Update the jump target
2598+ let last_mut = blocks[ current. idx ( ) ] . instructions . last_mut ( ) . unwrap ( ) ;
2599+ last_mut. target = new_idx;
2600+ predecessors[ target. idx ( ) ] -= 1 ;
2601+
2602+ current = blocks[ current. idx ( ) ] . next ;
2603+ }
2604+ }
2605+
24992606/// Duplicate `LOAD_CONST None + RETURN_VALUE` for blocks that fall through
2500- /// to the final return block. Matches CPython's behavior of ensuring every
2501- /// code path that reaches the end of a function/module has its own explicit
2502- /// return instruction.
2607+ /// to the final return block.
25032608fn duplicate_end_returns ( blocks : & mut [ Block ] ) {
25042609 // Walk the block chain to find the last block
25052610 let mut last_block = BlockIdx ( 0 ) ;
0 commit comments