@@ -149,7 +149,11 @@ pub fn domtree_build(func: &mut Function) {
149149 // Find new idom as intersection of all processed predecessors
150150 let mut new_idom: Option < usize > = None ;
151151 for parent_id in parents {
152- let parent_nr = postorder_nr[ & parent_id] ;
152+ // Skip predecessors that weren't reached during DFS (unreachable blocks)
153+ let parent_nr = match postorder_nr. get ( & parent_id) {
154+ Some ( & nr) => nr,
155+ None => continue ,
156+ } ;
153157 if doms[ parent_nr] . is_none ( ) {
154158 continue ;
155159 }
@@ -634,4 +638,62 @@ mod tests {
634638 assert ! ( func. dominates( BasicBlockId ( 3 ) , BasicBlockId ( 4 ) ) ) ;
635639 assert ! ( !func. dominates( BasicBlockId ( 3 ) , BasicBlockId ( 1 ) ) ) ;
636640 }
641+
642+ /// Test that domtree_build handles unreachable blocks gracefully.
643+ /// Unreachable blocks have predecessors that weren't visited during DFS.
644+ #[ test]
645+ fn test_domtree_with_unreachable_block ( ) {
646+ // Create a CFG with an unreachable block:
647+ // entry(0)
648+ // |
649+ // v
650+ // bb1(1)
651+ // |
652+ // v
653+ // exit(2)
654+ //
655+ // unreachable(3) <-- has edge from nowhere reachable
656+ // |
657+ // v
658+ // bb1(1) <-- unreachable points TO bb1, making bb1 have unreachable as predecessor
659+
660+ let types = TypeTable :: new ( 64 ) ;
661+ let mut func = Function :: new ( "test" , types. void_id ) ;
662+
663+ let mut entry = BasicBlock :: new ( BasicBlockId ( 0 ) ) ;
664+ entry. children = vec ! [ BasicBlockId ( 1 ) ] ;
665+ entry. add_insn ( Instruction :: new ( Opcode :: Entry ) ) ;
666+ entry. add_insn ( Instruction :: br ( BasicBlockId ( 1 ) ) ) ;
667+
668+ let mut bb1 = BasicBlock :: new ( BasicBlockId ( 1 ) ) ;
669+ // bb1 has both entry AND unreachable as predecessors
670+ bb1. parents = vec ! [ BasicBlockId ( 0 ) , BasicBlockId ( 3 ) ] ;
671+ bb1. children = vec ! [ BasicBlockId ( 2 ) ] ;
672+ bb1. add_insn ( Instruction :: br ( BasicBlockId ( 2 ) ) ) ;
673+
674+ let mut exit = BasicBlock :: new ( BasicBlockId ( 2 ) ) ;
675+ exit. parents = vec ! [ BasicBlockId ( 1 ) ] ;
676+ exit. add_insn ( Instruction :: ret ( None ) ) ;
677+
678+ // Unreachable block - not reachable from entry
679+ let mut unreachable = BasicBlock :: new ( BasicBlockId ( 3 ) ) ;
680+ unreachable. children = vec ! [ BasicBlockId ( 1 ) ] ;
681+ unreachable. add_insn ( Instruction :: br ( BasicBlockId ( 1 ) ) ) ;
682+
683+ func. entry = BasicBlockId ( 0 ) ;
684+ func. blocks = vec ! [ entry, bb1, exit, unreachable] ;
685+
686+ // This should not panic - the fix skips predecessors not reached during DFS
687+ domtree_build ( & mut func) ;
688+
689+ // Verify the reachable blocks have correct dominators
690+ let entry_block = func. get_block ( BasicBlockId ( 0 ) ) . unwrap ( ) ;
691+ assert ! ( entry_block. idom. is_none( ) ) ;
692+
693+ let bb1_block = func. get_block ( BasicBlockId ( 1 ) ) . unwrap ( ) ;
694+ assert_eq ! ( bb1_block. idom, Some ( BasicBlockId ( 0 ) ) ) ;
695+
696+ let exit_block = func. get_block ( BasicBlockId ( 2 ) ) . unwrap ( ) ;
697+ assert_eq ! ( exit_block. idom, Some ( BasicBlockId ( 1 ) ) ) ;
698+ }
637699}
0 commit comments