@@ -9,7 +9,7 @@ use std::cmp::Ordering;
9
9
use super :: {
10
10
basic_block:: BasicBlockId , cfg:: ControlFlowGraph , function:: Function , post_order:: PostOrder ,
11
11
} ;
12
- use fxhash:: FxHashMap as HashMap ;
12
+ use fxhash:: { FxHashMap as HashMap , FxHashSet as HashSet } ;
13
13
14
14
/// Dominator tree node. We keep one of these per reachable block.
15
15
#[ derive( Clone , Default ) ]
@@ -178,10 +178,10 @@ impl DominatorTree {
178
178
/// "Simple, Fast Dominator Algorithm."
179
179
fn compute_dominator_tree ( & mut self , cfg : & ControlFlowGraph , post_order : & PostOrder ) {
180
180
// We'll be iterating over a reverse post-order of the CFG, skipping the entry block.
181
- let ( entry_block_id, entry_free_post_order) = post_order
182
- . as_slice ( )
183
- . split_last ( )
184
- . expect ( "ICE: functions always have at least one block" ) ;
181
+ let Some ( ( entry_block_id, entry_free_post_order) ) = post_order. as_slice ( ) . split_last ( )
182
+ else {
183
+ return ;
184
+ } ;
185
185
186
186
// Do a first pass where we assign reverse post-order indices to all reachable nodes. The
187
187
// entry block will be the only node with no immediate dominator.
@@ -276,18 +276,67 @@ impl DominatorTree {
276
276
debug_assert_eq ! ( block_a_id, block_b_id, "Unreachable block passed to common_dominator?" ) ;
277
277
block_a_id
278
278
}
279
+
280
+ /// Computes the dominance frontier for all blocks in the dominator tree.
281
+ ///
282
+ /// This method uses the algorithm specified under Cooper, Keith D. et al. “A Simple, Fast Dominance Algorithm.” (1999).
283
+ /// As referenced in the paper a dominance frontier is the set of all CFG nodes, y, such that
284
+ /// b dominates a predecessor of y but does not strictly d.
285
+ ///
286
+ /// This method expects the appropriate CFG depending on whether we are operating over
287
+ /// a dominator tree (standard CFG) or a post-dominator tree (reversed CFG).
288
+ /// Calling this method on a dominator tree will return a function's dominance frontiers,
289
+ /// while on a post-dominator tree the method will return the function's reverse (or post) dominance frontiers.
290
+ pub ( crate ) fn compute_dominance_frontiers (
291
+ & mut self ,
292
+ cfg : & ControlFlowGraph ,
293
+ ) -> HashMap < BasicBlockId , HashSet < BasicBlockId > > {
294
+ let mut dominance_frontiers: HashMap < BasicBlockId , HashSet < BasicBlockId > > =
295
+ HashMap :: default ( ) ;
296
+
297
+ let nodes = self . nodes . keys ( ) . copied ( ) . collect :: < Vec < _ > > ( ) ;
298
+ for block_id in nodes {
299
+ let predecessors = cfg. predecessors ( block_id) ;
300
+ // Dominance frontier nodes must have more than one predecessor
301
+ if predecessors. len ( ) > 1 {
302
+ // Iterate over the predecessors of the current block
303
+ for pred_id in predecessors {
304
+ let mut runner = pred_id;
305
+ // We start by checking if the current block dominates the predecessor
306
+ while let Some ( immediate_dominator) = self . immediate_dominator ( block_id) {
307
+ if immediate_dominator != runner {
308
+ dominance_frontiers. entry ( runner) . or_default ( ) . insert ( block_id) ;
309
+ let Some ( runner_immediate_dom) = self . immediate_dominator ( runner)
310
+ else {
311
+ break ;
312
+ } ;
313
+ runner = runner_immediate_dom;
314
+ } else {
315
+ break ;
316
+ }
317
+ }
318
+ }
319
+ }
320
+ }
321
+
322
+ dominance_frontiers
323
+ }
279
324
}
280
325
281
326
#[ cfg( test) ]
282
327
mod tests {
283
328
use std:: cmp:: Ordering ;
284
329
330
+ use fxhash:: { FxHashMap as HashMap , FxHashSet as HashSet } ;
331
+
285
332
use crate :: ssa:: {
286
333
function_builder:: FunctionBuilder ,
287
334
ir:: {
288
- basic_block:: BasicBlockId , call_stack:: CallStackId , dom:: DominatorTree ,
289
- function:: Function , instruction:: TerminatorInstruction , map:: Id , types:: Type ,
335
+ basic_block:: BasicBlockId , call_stack:: CallStackId , cfg:: ControlFlowGraph ,
336
+ dom:: DominatorTree , function:: Function , instruction:: TerminatorInstruction , map:: Id ,
337
+ post_order:: PostOrder , types:: Type ,
290
338
} ,
339
+ ssa_gen:: Ssa ,
291
340
} ;
292
341
293
342
#[ test]
@@ -542,6 +591,164 @@ mod tests {
542
591
assert ! ( post_dom. dominates( block2_id, block2_id) ) ;
543
592
}
544
593
594
+ #[ test]
595
+ fn dom_frontiers_backwards_layout ( ) {
596
+ let ( func, ..) = backwards_layout_setup ( ) ;
597
+ let mut dt = DominatorTree :: with_function ( & func) ;
598
+
599
+ let cfg = ControlFlowGraph :: with_function ( & func) ;
600
+ let dom_frontiers = dt. compute_dominance_frontiers ( & cfg) ;
601
+ assert ! ( dom_frontiers. is_empty( ) ) ;
602
+ }
603
+
604
+ #[ test]
605
+ fn post_dom_frontiers_backwards_layout ( ) {
606
+ let ( func, ..) = backwards_layout_setup ( ) ;
607
+ let mut post_dom = DominatorTree :: with_function_post_dom ( & func) ;
608
+
609
+ let cfg = ControlFlowGraph :: with_function ( & func) ;
610
+ let dom_frontiers = post_dom. compute_dominance_frontiers ( & cfg) ;
611
+ assert ! ( dom_frontiers. is_empty( ) ) ;
612
+ }
613
+
614
+ fn loop_with_conditional ( ) -> Ssa {
615
+ let src = "
616
+ brillig(inline) predicate_pure fn main f0 {
617
+ b0(v1: u32, v2: u32):
618
+ v5 = eq v1, u32 5
619
+ jmp b1(u32 0)
620
+ b1(v3: u32):
621
+ v8 = lt v3, u32 4
622
+ jmpif v8 then: b2, else: b3
623
+ b2():
624
+ jmpif v5 then: b4, else: b5
625
+ b3():
626
+ return
627
+ b4():
628
+ v9 = mul u32 4294967295, v2
629
+ constrain v9 == u32 12
630
+ jmp b5()
631
+ b5():
632
+ v12 = unchecked_add v3, u32 1
633
+ jmp b1(v12)
634
+ }
635
+ " ;
636
+ Ssa :: from_str ( src) . unwrap ( )
637
+ }
638
+
639
+ #[ test]
640
+ fn dom_frontiers ( ) {
641
+ let ssa = loop_with_conditional ( ) ;
642
+ let main = ssa. main ( ) ;
643
+
644
+ let cfg = ControlFlowGraph :: with_function ( main) ;
645
+ let post_order = PostOrder :: with_cfg ( & cfg) ;
646
+
647
+ let mut dt = DominatorTree :: with_cfg_and_post_order ( & cfg, & post_order) ;
648
+ let dom_frontiers = dt. compute_dominance_frontiers ( & cfg) ;
649
+ dbg ! ( dom_frontiers. clone( ) ) ;
650
+
651
+ // Convert BasicBlockIds to their underlying u32 values for easy comparisons
652
+ let dom_frontiers = dom_frontiers
653
+ . into_iter ( )
654
+ . map ( |( block, frontier) | {
655
+ (
656
+ block. to_u32 ( ) ,
657
+ frontier. into_iter ( ) . map ( |block| block. to_u32 ( ) ) . collect :: < HashSet < _ > > ( ) ,
658
+ )
659
+ } )
660
+ . collect :: < HashMap < _ , _ > > ( ) ;
661
+
662
+ // b0 is the entry block which dominates all other blocks
663
+ // Thus, it has an empty set for its dominance frontier
664
+ assert ! ( !dom_frontiers. contains_key( & 0 ) ) ;
665
+
666
+ // b1 is in its own DF due to the loop b5 -> b1
667
+ // b1 dominates b5 which is a predecessor of b1, but b1 does not strictly dominate b1
668
+ let b1_df = & dom_frontiers[ & 1 ] ;
669
+ assert_eq ! ( b1_df. len( ) , 1 ) ;
670
+ assert ! ( b1_df. contains( & 1 ) ) ;
671
+
672
+ // b2 has DF { b1 } also due to the loop b5 -> b1.
673
+ // b2 dominates b5 which is a predecessor of b1, but b2 does not strictly dominate b1
674
+ let b2_df = & dom_frontiers[ & 2 ] ;
675
+ assert_eq ! ( b2_df. len( ) , 1 ) ;
676
+ assert ! ( b2_df. contains( & 1 ) ) ;
677
+
678
+ // b3 is the exit block which does not dominate any blocks
679
+ assert ! ( !dom_frontiers. contains_key( & 3 ) ) ;
680
+
681
+ // b4 has DF { b5 } because b4 jumps to b5 (thus being a predecessor to b5)
682
+ // b4 dominates itself but b5 is not strictly dominated by b4.
683
+ let b4_df = & dom_frontiers[ & 4 ] ;
684
+ assert_eq ! ( b4_df. len( ) , 1 ) ;
685
+ assert ! ( b4_df. contains( & 5 ) ) ;
686
+
687
+ // b5 has DF { b1 } also due to the loop b5 -> b1
688
+ let b5_df = & dom_frontiers[ & 5 ] ;
689
+ assert_eq ! ( b5_df. len( ) , 1 ) ;
690
+ assert ! ( b5_df. contains( & 1 ) ) ;
691
+ }
692
+
693
+ #[ test]
694
+ fn post_dom_frontiers ( ) {
695
+ let ssa = loop_with_conditional ( ) ;
696
+ let main = ssa. main ( ) ;
697
+
698
+ let cfg = ControlFlowGraph :: with_function ( main) ;
699
+ let reversed_cfg = cfg. reverse ( ) ;
700
+ let post_order = PostOrder :: with_cfg ( & reversed_cfg) ;
701
+
702
+ let mut post_dom = DominatorTree :: with_cfg_and_post_order ( & reversed_cfg, & post_order) ;
703
+ let post_dom_frontiers = post_dom. compute_dominance_frontiers ( & reversed_cfg) ;
704
+ dbg ! ( post_dom_frontiers. clone( ) ) ;
705
+
706
+ // Convert BasicBlockIds to their underlying u32 values for easy comparisons
707
+ let post_dom_frontiers = post_dom_frontiers
708
+ . into_iter ( )
709
+ . map ( |( block, frontier) | {
710
+ (
711
+ block. to_u32 ( ) ,
712
+ frontier. into_iter ( ) . map ( |block| block. to_u32 ( ) ) . collect :: < HashSet < _ > > ( ) ,
713
+ )
714
+ } )
715
+ . collect :: < HashMap < _ , _ > > ( ) ;
716
+
717
+ // Another way to think about the post-dominator frontier for a node n,
718
+ // is that we can reach a block in the PDF during execution without going through n.
719
+
720
+ // b0 is the entry node of the program and the exit block of the post-dominator tree.
721
+ // Thus, it has an empty set for its post-dominance frontier (PDF)
722
+ assert ! ( !post_dom_frontiers. contains_key( & 0 ) ) ;
723
+
724
+ // b1 is in its own PDF due to the loop b5 -> b1
725
+ // b1 post-dominates b2 and b5. b1 post-dominates itself but not strictly post-dominate itself.
726
+ let b1_pdf = & post_dom_frontiers[ & 1 ] ;
727
+ assert_eq ! ( b1_pdf. len( ) , 1 ) ;
728
+ assert ! ( b1_pdf. contains( & 1 ) ) ;
729
+
730
+ // b2 has DF { b1 } also due to the loop b5 -> b1.
731
+ // b1 post-dominates itself is a predecessor of b2, but b1 does not strictly post-dominate b2.
732
+ let b2_pdf = & post_dom_frontiers[ & 2 ] ;
733
+ assert_eq ! ( b2_pdf. len( ) , 1 ) ;
734
+ assert ! ( b2_pdf. contains( & 1 ) ) ;
735
+
736
+ // b3 is the exit block of the program, but the starting node of the post-dominator tree
737
+ // Thus, it has an empty PDF
738
+ assert ! ( !post_dom_frontiers. contains_key( & 3 ) ) ;
739
+
740
+ // b4 has DF { b2 } because b2 post-dominates itself and is a predecessor to b4.
741
+ // b2 does not strictly post-dominate b4.
742
+ let b4_pdf = & post_dom_frontiers[ & 4 ] ;
743
+ assert_eq ! ( b4_pdf. len( ) , 1 ) ;
744
+ assert ! ( b4_pdf. contains( & 2 ) ) ;
745
+
746
+ // b5 has DF { b1 } also due to the loop b5 -> b1
747
+ let b5_pdf = & post_dom_frontiers[ & 5 ] ;
748
+ assert_eq ! ( b5_pdf. len( ) , 1 ) ;
749
+ assert ! ( b5_pdf. contains( & 1 ) ) ;
750
+ }
751
+
545
752
#[ test]
546
753
fn test_find_map_dominator ( ) {
547
754
let ( dt, b0, b1, b2, _b3) = unreachable_node_setup ( ) ;
0 commit comments