33//! This module provides functionality to generate graph visualizations
44//! of Rust's MIR in various formats (DOT, D2).
55
6+ use std:: collections:: { HashMap , HashSet , VecDeque } ;
67use std:: fs:: File ;
78use std:: io:: { self , Write } ;
89
@@ -12,7 +13,11 @@ use rustc_middle::ty::TyCtxt;
1213extern crate rustc_session;
1314use rustc_session:: config:: { OutFileName , OutputType } ;
1415
15- use crate :: printer:: collect_smir;
16+ extern crate stable_mir;
17+ use stable_mir:: mir:: { ConstOperand , Operand , TerminatorKind } ;
18+
19+ use crate :: printer:: { collect_smir, Item } ;
20+ use crate :: MonoItemKind ;
1621
1722// Sub-modules
1823pub mod context;
@@ -35,17 +40,125 @@ pub(crate) fn skip_lang_start() -> bool {
3540 * VAR . get_or_init ( || std:: env:: var ( "SKIP_LANG_START" ) . is_ok ( ) )
3641}
3742
43+ /// Compute the set of symbol names to exclude from graph rendering.
44+ /// Excludes `std::rt::lang_start` items and items uniquely downstream
45+ /// of them (i.e., only reachable through `lang_start` in the call graph).
46+ ///
47+ /// The algorithm:
48+ /// 1. Build a call graph from Call terminators
49+ /// 2. Identify `std::rt::lang_start` seed items (via demangled name of MonoItemFn)
50+ /// 3. Find entry-point items (not called by any other item)
51+ /// 4. BFS from non-seed entry points, not entering seed nodes
52+ /// 5. Everything not reachable gets excluded
53+ pub ( crate ) fn compute_lang_start_exclusions ( items : & [ Item ] , ctx : & GraphContext ) -> HashSet < String > {
54+ // Build forward call graph: symbol_name -> list of callee names
55+ let mut call_graph: HashMap < & str , Vec < & str > > = HashMap :: new ( ) ;
56+ for item in items {
57+ if let MonoItemKind :: MonoItemFn {
58+ body : Some ( body) , ..
59+ } = & item. mono_item_kind
60+ {
61+ let callees: Vec < & str > = body
62+ . blocks
63+ . iter ( )
64+ . filter_map ( |block| {
65+ if let TerminatorKind :: Call {
66+ func : Operand :: Constant ( ConstOperand { const_, .. } ) ,
67+ ..
68+ } = & block. terminator . kind
69+ {
70+ return ctx. functions . get ( & const_. ty ( ) ) . map ( |s| s. as_str ( ) ) ;
71+ }
72+ None
73+ } )
74+ . collect ( ) ;
75+ call_graph. insert ( & item. symbol_name , callees) ;
76+ }
77+ }
78+
79+ // Identify seed items via the demangled MonoItemFn name containing "std::rt::lang_start".
80+ let seed_names: HashSet < & str > = items
81+ . iter ( )
82+ . filter ( |item| is_std_rt_lang_start ( & item. mono_item_kind ) )
83+ . map ( |item| item. symbol_name . as_str ( ) )
84+ . collect ( ) ;
85+
86+ // Retrieve all items that were called via a Call terminator
87+ let has_callers: HashSet < & str > = call_graph. values ( ) . flatten ( ) . copied ( ) . collect ( ) ;
88+
89+ // BFS from non-seed entry points (items with no callers)
90+ let mut reachable: HashSet < & str > = HashSet :: new ( ) ;
91+ let mut queue: VecDeque < & str > = VecDeque :: new ( ) ;
92+
93+ for item in items {
94+ let name = item. symbol_name . as_str ( ) ;
95+ let is_entry = !has_callers. contains ( name) ;
96+ if is_entry && !seed_names. contains ( name) {
97+ // some items call other items
98+ reachable. insert ( name) ;
99+ queue. push_back ( name) ;
100+ }
101+ }
102+
103+ while let Some ( name) = queue. pop_front ( ) {
104+ if let Some ( callees) = call_graph. get ( name) {
105+ for & callee in callees {
106+ if !reachable. contains ( callee) && !seed_names. contains ( callee) {
107+ reachable. insert ( callee) ;
108+ queue. push_back ( callee) ;
109+ }
110+ }
111+ }
112+ }
113+
114+ // Everything NOT reachable should be excluded
115+ let all_names: HashSet < & str > = items
116+ . iter ( )
117+ . map ( |i| i. symbol_name . as_str ( ) )
118+ . chain ( ctx. functions . values ( ) . map ( |s| s. as_str ( ) ) ) // chain external functions too
119+ . collect ( ) ;
120+
121+ all_names
122+ . difference ( & reachable)
123+ . map ( |s| s. to_string ( ) )
124+ . collect ( )
125+ }
126+
127+ /// Check the demangled MonoItemFn name for `std::rt::lang_start`.
128+ /// This catches:
129+ /// - `std::rt::lang_start::<()>` (the runtime entry point)
130+ /// - `std::rt::lang_start::<()>::{closure#0}` (its closure)
131+ /// - `<{closure@std::rt::lang_start<()>::{closure#0}} as ...>::call_once` (trait impls referencing it)
132+ /// - `std::ptr::drop_in_place::<{closure@std::rt::lang_start<()>::{closure#0}}>` (drop glue)
133+ ///
134+ /// But not a user-defined `lang_start` e.g. `crate1::something::lang_start`.
135+ fn is_std_rt_lang_start ( kind : & MonoItemKind ) -> bool {
136+ match kind {
137+ MonoItemKind :: MonoItemFn { name, .. } => name. contains ( "std::rt::lang_start" ) ,
138+ _ => false ,
139+ }
140+ }
141+
38142// =============================================================================
39143// Entry Points
40144// =============================================================================
41145
42146/// Entry point to write the DOT file
43147pub fn emit_dotfile ( tcx : TyCtxt < ' _ > ) {
148+ let smir = collect_smir ( tcx) ;
149+
44150 if skip_lang_start ( ) {
45- println ! ( "SKIP_LANG_START is set" ) ;
151+ let ctx = GraphContext :: from_smir ( & smir) ;
152+ let excluded = compute_lang_start_exclusions ( & smir. items , & ctx) ;
153+ println ! ( "SKIP_LANG_START: excluding {} items:" , excluded. len( ) ) ;
154+ let mut sorted: Vec < _ > = excluded. iter ( ) . collect ( ) ;
155+ sorted. sort ( ) ;
156+ for name in sorted {
157+ println ! ( " - {}" , name) ;
158+ }
46159 }
47160
48- let smir_dot = collect_smir ( tcx ) . to_dot_file ( ) ;
161+ let smir_dot = smir . to_dot_file ( ) ;
49162
50163 match tcx. output_filenames ( ( ) ) . path ( OutputType :: Mir ) {
51164 OutFileName :: Stdout => {
0 commit comments