@@ -44,10 +44,17 @@ pub fn collect_reachable_items<'tcx>(
44
44
) -> Vec < MonoItem < ' tcx > > {
45
45
// For each harness, collect items using the same collector.
46
46
// I.e.: This will return any item that is reachable from one or more of the starting points.
47
- let mut collector = MonoItemsCollector { tcx , collected : FxHashSet :: default ( ) , queue : vec ! [ ] } ;
47
+ let mut collector = MonoItemsCollector :: new ( tcx ) ;
48
48
for item in starting_points {
49
49
collector. collect ( * item) ;
50
50
}
51
+
52
+ #[ cfg( debug_assertions) ]
53
+ collector
54
+ . call_graph
55
+ . dump_dot ( tcx)
56
+ . unwrap_or_else ( |e| tracing:: error!( "Failed to dump call graph: {e}" ) ) ;
57
+
51
58
tcx. sess . abort_if_errors ( ) ;
52
59
53
60
// Sort the result so code generation follows deterministic order.
@@ -140,9 +147,20 @@ struct MonoItemsCollector<'tcx> {
140
147
collected : FxHashSet < MonoItem < ' tcx > > ,
141
148
/// Items enqueued for visiting.
142
149
queue : Vec < MonoItem < ' tcx > > ,
150
+ #[ cfg( debug_assertions) ]
151
+ call_graph : debug:: CallGraph < ' tcx > ,
143
152
}
144
153
145
154
impl < ' tcx > MonoItemsCollector < ' tcx > {
155
+ pub fn new ( tcx : TyCtxt < ' tcx > ) -> Self {
156
+ MonoItemsCollector {
157
+ tcx,
158
+ collected : FxHashSet :: default ( ) ,
159
+ queue : vec ! [ ] ,
160
+ #[ cfg( debug_assertions) ]
161
+ call_graph : debug:: CallGraph :: default ( ) ,
162
+ }
163
+ }
146
164
/// Collects all reachable items starting from the given root.
147
165
pub fn collect ( & mut self , root : MonoItem < ' tcx > ) {
148
166
debug ! ( ?root, "collect" ) ;
@@ -156,52 +174,56 @@ impl<'tcx> MonoItemsCollector<'tcx> {
156
174
while let Some ( to_visit) = self . queue . pop ( ) {
157
175
if !self . collected . contains ( & to_visit) {
158
176
self . collected . insert ( to_visit) ;
159
- match to_visit {
160
- MonoItem :: Fn ( instance) => {
161
- self . visit_fn ( instance) ;
162
- }
163
- MonoItem :: Static ( def_id) => {
164
- self . visit_static ( def_id) ;
165
- }
177
+ let next_items = match to_visit {
178
+ MonoItem :: Fn ( instance) => self . visit_fn ( instance) ,
179
+ MonoItem :: Static ( def_id) => self . visit_static ( def_id) ,
166
180
MonoItem :: GlobalAsm ( _) => {
167
181
self . visit_asm ( to_visit) ;
182
+ vec ! [ ]
168
183
}
169
- }
184
+ } ;
185
+ #[ cfg( debug_assertions) ]
186
+ self . call_graph . add_edges ( to_visit, & next_items) ;
187
+
188
+ self . queue
189
+ . extend ( next_items. into_iter ( ) . filter ( |item| !self . collected . contains ( item) ) ) ;
170
190
}
171
191
}
172
192
}
173
193
174
194
/// Visit a function and collect all mono-items reachable from its instructions.
175
- fn visit_fn ( & mut self , instance : Instance < ' tcx > ) {
195
+ fn visit_fn ( & mut self , instance : Instance < ' tcx > ) -> Vec < MonoItem < ' tcx > > {
176
196
let _guard = debug_span ! ( "visit_fn" , function=?instance) . entered ( ) ;
177
197
let body = self . tcx . instance_mir ( instance. def ) ;
178
198
let mut collector =
179
199
MonoItemsFnCollector { tcx : self . tcx , collected : FxHashSet :: default ( ) , instance, body } ;
180
200
collector. visit_body ( body) ;
181
- self . queue . extend ( collector. collected . iter ( ) . filter ( |item| ! self . collected . contains ( item ) ) ) ;
201
+ collector. collected . into_iter ( ) . collect ( )
182
202
}
183
203
184
204
/// Visit a static object and collect drop / initialization functions.
185
- fn visit_static ( & mut self , def_id : DefId ) {
205
+ fn visit_static ( & mut self , def_id : DefId ) -> Vec < MonoItem < ' tcx > > {
186
206
let _guard = debug_span ! ( "visit_static" , ?def_id) . entered ( ) ;
187
207
let instance = Instance :: mono ( self . tcx , def_id) ;
208
+ let mut next_items = vec ! [ ] ;
188
209
189
210
// Collect drop function.
190
211
let static_ty = instance. ty ( self . tcx , ParamEnv :: reveal_all ( ) ) ;
191
212
let instance = Instance :: resolve_drop_in_place ( self . tcx , static_ty) ;
192
- self . queue . push ( MonoItem :: Fn ( instance. polymorphize ( self . tcx ) ) ) ;
213
+ next_items . push ( MonoItem :: Fn ( instance. polymorphize ( self . tcx ) ) ) ;
193
214
194
215
// Collect initialization.
195
216
let alloc = self . tcx . eval_static_initializer ( def_id) . unwrap ( ) ;
196
217
for id in alloc. inner ( ) . provenance ( ) . provenances ( ) {
197
- self . queue . extend ( collect_alloc_items ( self . tcx , id) . iter ( ) ) ;
218
+ next_items . extend ( collect_alloc_items ( self . tcx , id) . iter ( ) ) ;
198
219
}
220
+
221
+ next_items
199
222
}
200
223
201
224
/// Visit global assembly and collect its item.
202
225
fn visit_asm ( & mut self , item : MonoItem < ' tcx > ) {
203
226
debug ! ( ?item, "visit_asm" ) ;
204
- self . collected . insert ( item) ;
205
227
}
206
228
}
207
229
@@ -626,3 +648,113 @@ impl<'a, 'tcx> MirVisitor<'tcx> for ConstMonoItemExtractor<'a, 'tcx> {
626
648
self . super_rvalue ( rvalue, location) ;
627
649
}
628
650
}
651
+
652
+ #[ cfg( debug_assertions) ]
653
+ mod debug {
654
+ #![ allow( dead_code) ]
655
+
656
+ use std:: {
657
+ collections:: { HashMap , HashSet } ,
658
+ fs:: File ,
659
+ io:: { BufWriter , Write } ,
660
+ } ;
661
+
662
+ use rustc_session:: config:: OutputType ;
663
+
664
+ use super :: * ;
665
+
666
+ #[ derive( Debug , Default ) ]
667
+ pub struct CallGraph < ' tcx > {
668
+ // Nodes of the graph.
669
+ nodes : HashSet < Node < ' tcx > > ,
670
+ edges : HashMap < Node < ' tcx > , Vec < Node < ' tcx > > > ,
671
+ back_edges : HashMap < Node < ' tcx > , Vec < Node < ' tcx > > > ,
672
+ }
673
+
674
+ type Node < ' tcx > = MonoItem < ' tcx > ;
675
+
676
+ impl < ' tcx > CallGraph < ' tcx > {
677
+ pub fn add_node ( & mut self , item : Node < ' tcx > ) {
678
+ self . nodes . insert ( item) ;
679
+ self . edges . entry ( item) . or_default ( ) ;
680
+ self . back_edges . entry ( item) . or_default ( ) ;
681
+ }
682
+
683
+ /// Add a new edge "from" -> "to".
684
+ pub fn add_edge ( & mut self , from : Node < ' tcx > , to : Node < ' tcx > ) {
685
+ self . add_node ( from) ;
686
+ self . add_node ( to) ;
687
+ self . edges . get_mut ( & from) . unwrap ( ) . push ( to) ;
688
+ self . back_edges . get_mut ( & to) . unwrap ( ) . push ( from) ;
689
+ }
690
+
691
+ /// Add multiple new edges for the "from" node.
692
+ pub fn add_edges ( & mut self , from : Node < ' tcx > , to : & [ Node < ' tcx > ] ) {
693
+ self . add_node ( from) ;
694
+ for item in to {
695
+ self . add_edge ( from, * item) ;
696
+ }
697
+ }
698
+
699
+ /// Print the graph in DOT format to a file.
700
+ /// See <https://graphviz.org/doc/info/lang.html> for more information.
701
+ pub fn dump_dot ( & self , tcx : TyCtxt ) -> std:: io:: Result < ( ) > {
702
+ if let Ok ( target) = std:: env:: var ( "KANI_REACH_DEBUG" ) {
703
+ debug ! ( ?target, "dump_dot" ) ;
704
+ let outputs = tcx. output_filenames ( ( ) ) ;
705
+ let path = outputs. output_path ( OutputType :: Metadata ) . with_extension ( "dot" ) ;
706
+ let out_file = File :: create ( & path) ?;
707
+ let mut writer = BufWriter :: new ( out_file) ;
708
+ writeln ! ( writer, "digraph ReachabilityGraph {{" ) ?;
709
+ if target. is_empty ( ) {
710
+ self . dump_all ( & mut writer) ?;
711
+ } else {
712
+ // Only dump nodes that led the reachability analysis to the target node.
713
+ self . dump_reason ( & mut writer, & target) ?;
714
+ }
715
+ writeln ! ( writer, "}}" ) ?;
716
+ }
717
+
718
+ Ok ( ( ) )
719
+ }
720
+
721
+ /// Write all notes to the given writer.
722
+ fn dump_all < W : Write > ( & self , writer : & mut W ) -> std:: io:: Result < ( ) > {
723
+ tracing:: info!( nodes=?self . nodes. len( ) , edges=?self . edges. len( ) , "dump_all" ) ;
724
+ for node in & self . nodes {
725
+ writeln ! ( writer, r#""{node}""# ) ?;
726
+ for succ in self . edges . get ( node) . unwrap ( ) {
727
+ writeln ! ( writer, r#""{node}" -> "{succ}" "# ) ?;
728
+ }
729
+ }
730
+ Ok ( ( ) )
731
+ }
732
+
733
+ /// Write all notes that may have led to the discovery of the given target.
734
+ fn dump_reason < W : Write > ( & self , writer : & mut W , target : & str ) -> std:: io:: Result < ( ) > {
735
+ let mut queue = self
736
+ . nodes
737
+ . iter ( )
738
+ . filter ( |item| item. to_string ( ) . contains ( target) )
739
+ . collect :: < Vec < _ > > ( ) ;
740
+ let mut visited: HashSet < & MonoItem > = HashSet :: default ( ) ;
741
+ tracing:: info!( target=?queue, nodes=?self . nodes. len( ) , edges=?self . edges. len( ) , "dump_reason" ) ;
742
+ while let Some ( to_visit) = queue. pop ( ) {
743
+ if !visited. contains ( to_visit) {
744
+ visited. insert ( to_visit) ;
745
+ queue. extend ( self . back_edges . get ( to_visit) . unwrap ( ) ) ;
746
+ }
747
+ }
748
+
749
+ for node in & visited {
750
+ writeln ! ( writer, r#""{node}""# ) ?;
751
+ for succ in
752
+ self . edges . get ( node) . unwrap ( ) . iter ( ) . filter ( |item| visited. contains ( item) )
753
+ {
754
+ writeln ! ( writer, r#""{node}" -> "{succ}" "# ) ?;
755
+ }
756
+ }
757
+ Ok ( ( ) )
758
+ }
759
+ }
760
+ }
0 commit comments