1+ use alloc:: collections:: VecDeque ;
12use core:: ops;
23
34use crate :: { IndexMap , IndexSet , error:: InternalError } ;
@@ -132,6 +133,9 @@ pub struct Block {
132133 pub preserve_lasti : bool ,
133134 /// Stack depth at block entry, set by stack depth analysis
134135 pub start_depth : Option < u32 > ,
136+ /// Whether this block is "cold" (only reachable via exception table).
137+ /// Cold blocks are pushed to the end during optimization.
138+ pub cold : bool ,
135139}
136140
137141impl Default for Block {
@@ -142,6 +146,7 @@ impl Default for Block {
142146 except_handler : false ,
143147 preserve_lasti : false ,
144148 start_depth : None ,
149+ cold : false ,
145150 }
146151 }
147152}
@@ -205,6 +210,8 @@ impl CodeInfo {
205210 // Post-codegen CFG analysis passes (flowgraph.c pipeline)
206211 mark_except_handlers ( & mut self . blocks ) ;
207212 label_exception_targets ( & mut self . blocks ) ;
213+ push_cold_blocks_to_end ( & mut self . blocks ) ;
214+ normalize_jumps ( & mut self . blocks ) ;
208215
209216 let max_stackdepth = self . max_stackdepth ( ) ?;
210217 let cell2arg = self . cell2arg ( ) ;
@@ -1154,6 +1161,248 @@ pub(crate) fn mark_except_handlers(blocks: &mut [Block]) {
11541161 }
11551162}
11561163
1164+ /// Mark cold blocks: blocks only reachable via exception table.
1165+ /// BFS from entry following normal control flow (next + jump targets),
1166+ /// skipping edges into except_handler blocks. Unvisited blocks are cold.
1167+ /// flowgraph.c mark_cold
1168+ fn mark_cold ( blocks : & mut [ Block ] ) {
1169+ let n = blocks. len ( ) ;
1170+ let mut warm = vec ! [ false ; n] ;
1171+ let mut queue = VecDeque :: new ( ) ;
1172+
1173+ // Entry block is always warm
1174+ warm[ 0 ] = true ;
1175+ queue. push_back ( BlockIdx ( 0 ) ) ;
1176+
1177+ while let Some ( block_idx) = queue. pop_front ( ) {
1178+ let block = & blocks[ block_idx. idx ( ) ] ;
1179+
1180+ // Follow fall-through (block.next)
1181+ let has_fallthrough = block
1182+ . instructions
1183+ . last ( )
1184+ . map ( |ins| !ins. instr . is_scope_exit ( ) && !ins. instr . is_unconditional_jump ( ) )
1185+ . unwrap_or ( true ) ;
1186+ if has_fallthrough && block. next != BlockIdx :: NULL {
1187+ let next_idx = block. next . idx ( ) ;
1188+ if !blocks[ next_idx] . except_handler && !warm[ next_idx] {
1189+ warm[ next_idx] = true ;
1190+ queue. push_back ( block. next ) ;
1191+ }
1192+ }
1193+
1194+ // Follow jump targets in instructions
1195+ for instr in & block. instructions {
1196+ if instr. target != BlockIdx :: NULL {
1197+ let target_idx = instr. target . idx ( ) ;
1198+ if !blocks[ target_idx] . except_handler && !warm[ target_idx] {
1199+ warm[ target_idx] = true ;
1200+ queue. push_back ( instr. target ) ;
1201+ }
1202+ }
1203+ }
1204+ }
1205+
1206+ // Mark non-warm blocks as cold
1207+ for ( i, block) in blocks. iter_mut ( ) . enumerate ( ) {
1208+ block. cold = !warm[ i] ;
1209+ }
1210+ }
1211+
1212+ /// Reorder the block linked list to push cold blocks to the end.
1213+ /// If a cold block falls through to a warm block, insert an explicit
1214+ /// JUMP_NO_INTERRUPT to maintain control flow.
1215+ /// flowgraph.c push_cold_blocks_to_end
1216+ fn push_cold_blocks_to_end ( blocks : & mut Vec < Block > ) {
1217+ // Single block, nothing to reorder
1218+ if blocks. len ( ) <= 1 {
1219+ return ;
1220+ }
1221+
1222+ mark_cold ( blocks) ;
1223+
1224+ // If a cold block falls through to a warm block, add an explicit jump
1225+ let fixups: Vec < ( BlockIdx , BlockIdx ) > = iter_blocks ( blocks)
1226+ . filter ( |( _, block) | {
1227+ block. cold
1228+ && block. next != BlockIdx :: NULL
1229+ && !blocks[ block. next . idx ( ) ] . cold
1230+ && block
1231+ . instructions
1232+ . last ( )
1233+ . map ( |ins| !ins. instr . is_scope_exit ( ) && !ins. instr . is_unconditional_jump ( ) )
1234+ . unwrap_or ( true )
1235+ } )
1236+ . map ( |( idx, block) | ( idx, block. next ) )
1237+ . collect ( ) ;
1238+
1239+ for ( cold_idx, warm_next) in fixups {
1240+ // Create a new block with an explicit jump
1241+ let jump_block_idx = BlockIdx ( blocks. len ( ) as u32 ) ;
1242+ let loc = blocks[ cold_idx. idx ( ) ]
1243+ . instructions
1244+ . last ( )
1245+ . map ( |i| i. location )
1246+ . unwrap_or_default ( ) ;
1247+ let end_loc = blocks[ cold_idx. idx ( ) ]
1248+ . instructions
1249+ . last ( )
1250+ . map ( |i| i. end_location )
1251+ . unwrap_or_default ( ) ;
1252+ let mut jump_block = Block {
1253+ cold : true ,
1254+ ..Block :: default ( )
1255+ } ;
1256+ jump_block. instructions . push ( InstructionInfo {
1257+ instr : PseudoInstruction :: JumpNoInterrupt {
1258+ target : Arg :: marker ( ) ,
1259+ }
1260+ . into ( ) ,
1261+ arg : OpArg :: new ( 0 ) ,
1262+ target : warm_next,
1263+ location : loc,
1264+ end_location : end_loc,
1265+ except_handler : None ,
1266+ lineno_override : None ,
1267+ } ) ;
1268+ jump_block. next = blocks[ cold_idx. idx ( ) ] . next ;
1269+ blocks[ cold_idx. idx ( ) ] . next = jump_block_idx;
1270+ blocks. push ( jump_block) ;
1271+ }
1272+
1273+ // Now reorder: extract cold block streaks and append at the end
1274+ let mut cold_head: BlockIdx = BlockIdx :: NULL ;
1275+ let mut cold_tail: BlockIdx = BlockIdx :: NULL ;
1276+
1277+ // Walk the chain, collect and remove cold blocks
1278+ let mut current = BlockIdx ( 0 ) ;
1279+ // Entry block should never be cold
1280+ assert ! ( !blocks[ 0 ] . cold) ;
1281+
1282+ while current != BlockIdx :: NULL {
1283+ let next = blocks[ current. idx ( ) ] . next ;
1284+ if next == BlockIdx :: NULL {
1285+ break ;
1286+ }
1287+
1288+ if blocks[ next. idx ( ) ] . cold {
1289+ // Start of a cold streak
1290+ let cold_start = next;
1291+ let mut cold_end = next;
1292+ while blocks[ cold_end. idx ( ) ] . next != BlockIdx :: NULL
1293+ && blocks[ blocks[ cold_end. idx ( ) ] . next . idx ( ) ] . cold
1294+ {
1295+ cold_end = blocks[ cold_end. idx ( ) ] . next ;
1296+ }
1297+
1298+ // Unlink cold streak from main chain
1299+ let after_cold = blocks[ cold_end. idx ( ) ] . next ;
1300+ blocks[ current. idx ( ) ] . next = after_cold;
1301+ blocks[ cold_end. idx ( ) ] . next = BlockIdx :: NULL ;
1302+
1303+ // Append to cold list
1304+ if cold_head == BlockIdx :: NULL {
1305+ cold_head = cold_start;
1306+ } else {
1307+ blocks[ cold_tail. idx ( ) ] . next = cold_start;
1308+ }
1309+ cold_tail = cold_end;
1310+
1311+ // Don't advance current - check the new next
1312+ } else {
1313+ current = next;
1314+ }
1315+ }
1316+
1317+ // Append cold blocks at the end of main chain
1318+ if cold_head != BlockIdx :: NULL {
1319+ // Find end of main chain
1320+ let mut last = current;
1321+ while blocks[ last. idx ( ) ] . next != BlockIdx :: NULL {
1322+ last = blocks[ last. idx ( ) ] . next ;
1323+ }
1324+ blocks[ last. idx ( ) ] . next = cold_head;
1325+ }
1326+ }
1327+
1328+ /// Returns true if the instruction is a conditional jump (POP_JUMP_IF_*).
1329+ fn is_conditional_jump ( instr : & AnyInstruction ) -> bool {
1330+ matches ! (
1331+ instr. real( ) ,
1332+ Some (
1333+ Instruction :: PopJumpIfFalse { .. }
1334+ | Instruction :: PopJumpIfTrue { .. }
1335+ | Instruction :: PopJumpIfNone { .. }
1336+ | Instruction :: PopJumpIfNotNone { .. }
1337+ )
1338+ )
1339+ }
1340+
1341+ /// Remove redundant unconditional jumps and add NOT_TAKEN after forward
1342+ /// conditional jumps (scanning all instructions in each block).
1343+ /// flowgraph.c normalize_jumps + remove_redundant_jumps
1344+ fn normalize_jumps ( blocks : & mut [ Block ] ) {
1345+ // Walk linked list to determine visit order (for forward/backward detection)
1346+ let mut visit_order = Vec :: new ( ) ;
1347+ let mut visited = vec ! [ false ; blocks. len( ) ] ;
1348+ let mut current = BlockIdx ( 0 ) ;
1349+ while current != BlockIdx :: NULL {
1350+ visit_order. push ( current) ;
1351+ visited[ current. idx ( ) ] = true ;
1352+ current = blocks[ current. idx ( ) ] . next ;
1353+ }
1354+
1355+ // Reset visited for forward/backward detection during second pass
1356+ visited. fill ( false ) ;
1357+
1358+ for block_idx in visit_order {
1359+ let idx = block_idx. idx ( ) ;
1360+ visited[ idx] = true ;
1361+
1362+ // Remove redundant unconditional jump to next block
1363+ let next = blocks[ idx] . next ;
1364+ if next != BlockIdx :: NULL {
1365+ let last = blocks[ idx] . instructions . last ( ) ;
1366+ let is_jump_to_next = last. is_some_and ( |ins| {
1367+ ins. instr . is_unconditional_jump ( )
1368+ && ins. target != BlockIdx :: NULL
1369+ && ins. target == next
1370+ } ) ;
1371+ if is_jump_to_next && let Some ( last_instr) = blocks[ idx] . instructions . last_mut ( ) {
1372+ last_instr. instr = Instruction :: Nop . into ( ) ;
1373+ last_instr. target = BlockIdx :: NULL ;
1374+ }
1375+ }
1376+
1377+ // Collect positions where NOT_TAKEN should be inserted
1378+ let mut insert_positions: Vec < ( usize , InstructionInfo ) > = Vec :: new ( ) ;
1379+ for ( i, ins) in blocks[ idx] . instructions . iter ( ) . enumerate ( ) {
1380+ if is_conditional_jump ( & ins. instr )
1381+ && ins. target != BlockIdx :: NULL
1382+ && !visited[ ins. target . idx ( ) ]
1383+ {
1384+ insert_positions. push ( (
1385+ i + 1 ,
1386+ InstructionInfo {
1387+ instr : Instruction :: NotTaken . into ( ) ,
1388+ arg : OpArg :: new ( 0 ) ,
1389+ target : BlockIdx :: NULL ,
1390+ location : ins. location ,
1391+ end_location : ins. end_location ,
1392+ except_handler : ins. except_handler ,
1393+ lineno_override : None ,
1394+ } ,
1395+ ) ) ;
1396+ }
1397+ }
1398+
1399+ // Insert NOT_TAKEN in reverse order to preserve indices
1400+ for ( pos, info) in insert_positions. into_iter ( ) . rev ( ) {
1401+ blocks[ idx] . instructions . insert ( pos, info) ;
1402+ }
1403+ }
1404+ }
1405+
11571406/// Label exception targets: walk CFG with except stack, set per-instruction
11581407/// handler info and block preserve_lasti flag. Converts POP_BLOCK to NOP.
11591408/// flowgraph.c label_exception_targets + push_except_block
0 commit comments