Skip to content

Commit 9ab8101

Browse files
committed
Added filtering of std::rt::lang_start items
This filters out all `lang_start` items (functions, closures, drop glue etc.). It only removes the items if they are `std::rt::lang_start` or are exclusively reachable by those functions. That means `black_box` is removed if it is not called otherwise in the program, but if it is then it is not removed. There is a couple of stages to get the data together in the right form so that only the items desired to be removed are.
1 parent 34f91cb commit 9ab8101

1 file changed

Lines changed: 116 additions & 3 deletions

File tree

src/mk_graph/mod.rs

Lines changed: 116 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
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};
67
use std::fs::File;
78
use std::io::{self, Write};
89

@@ -12,7 +13,11 @@ use rustc_middle::ty::TyCtxt;
1213
extern crate rustc_session;
1314
use 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
1823
pub 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
43147
pub 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

Comments
 (0)