@@ -44,10 +44,17 @@ pub fn collect_reachable_items<'tcx>(
4444) -> Vec < MonoItem < ' tcx > > {
4545 // For each harness, collect items using the same collector.
4646 // 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 ) ;
4848 for item in starting_points {
4949 collector. collect ( * item) ;
5050 }
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+
5158 tcx. sess . abort_if_errors ( ) ;
5259
5360 // Sort the result so code generation follows deterministic order.
@@ -140,9 +147,20 @@ struct MonoItemsCollector<'tcx> {
140147 collected : FxHashSet < MonoItem < ' tcx > > ,
141148 /// Items enqueued for visiting.
142149 queue : Vec < MonoItem < ' tcx > > ,
150+ #[ cfg( debug_assertions) ]
151+ call_graph : debug:: CallGraph < ' tcx > ,
143152}
144153
145154impl < ' 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+ }
146164 /// Collects all reachable items starting from the given root.
147165 pub fn collect ( & mut self , root : MonoItem < ' tcx > ) {
148166 debug ! ( ?root, "collect" ) ;
@@ -156,52 +174,56 @@ impl<'tcx> MonoItemsCollector<'tcx> {
156174 while let Some ( to_visit) = self . queue . pop ( ) {
157175 if !self . collected . contains ( & to_visit) {
158176 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) ,
166180 MonoItem :: GlobalAsm ( _) => {
167181 self . visit_asm ( to_visit) ;
182+ vec ! [ ]
168183 }
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) ) ) ;
170190 }
171191 }
172192 }
173193
174194 /// 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 > > {
176196 let _guard = debug_span ! ( "visit_fn" , function=?instance) . entered ( ) ;
177197 let body = self . tcx . instance_mir ( instance. def ) ;
178198 let mut collector =
179199 MonoItemsFnCollector { tcx : self . tcx , collected : FxHashSet :: default ( ) , instance, body } ;
180200 collector. visit_body ( body) ;
181- self . queue . extend ( collector. collected . iter ( ) . filter ( |item| ! self . collected . contains ( item ) ) ) ;
201+ collector. collected . into_iter ( ) . collect ( )
182202 }
183203
184204 /// 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 > > {
186206 let _guard = debug_span ! ( "visit_static" , ?def_id) . entered ( ) ;
187207 let instance = Instance :: mono ( self . tcx , def_id) ;
208+ let mut next_items = vec ! [ ] ;
188209
189210 // Collect drop function.
190211 let static_ty = instance. ty ( self . tcx , ParamEnv :: reveal_all ( ) ) ;
191212 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 ) ) ) ;
193214
194215 // Collect initialization.
195216 let alloc = self . tcx . eval_static_initializer ( def_id) . unwrap ( ) ;
196217 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 ( ) ) ;
198219 }
220+
221+ next_items
199222 }
200223
201224 /// Visit global assembly and collect its item.
202225 fn visit_asm ( & mut self , item : MonoItem < ' tcx > ) {
203226 debug ! ( ?item, "visit_asm" ) ;
204- self . collected . insert ( item) ;
205227 }
206228}
207229
@@ -626,3 +648,113 @@ impl<'a, 'tcx> MirVisitor<'tcx> for ConstMonoItemExtractor<'a, 'tcx> {
626648 self . super_rvalue ( rvalue, location) ;
627649 }
628650}
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