Skip to content

Commit 3ec8b67

Browse files
Rollup merge of #144779 - Kobzol:bootstrap-dot, r=jieyouxu
Implement debugging output of the bootstrap Step graph into a DOT file There are already a bunch of ways how we can debug bootstrap, so why not add one more =D (ideally I'd like to consolidate these approaches somewhat, ```@Shourya742``` is looking into that, but I think that this specific debugging tool is orthogonal to the rest of them, and is quite useful). This PR adds the option to render the bootstrap step graph into the DOT format, in order to understand what steps were executed, along with their fields (`Debug` output). Here you can see an example of the generated DOT files for the `BOOTSTRAP_TRACING=1 ./x build compiler --stage 2 --dry-run` command on x64 Linux. One is with cached deps (what this PR does), the other one without. [bootstrap-dot.zip](https://github.com/user-attachments/files/21548679/bootstrap-dot.zip) Visual example: <img width="1899" height="445" alt="image" src="https://github.com/user-attachments/assets/ae40e6d2-0ea8-48bb-b77e-6b21700b95ee" /> r? ```@jieyouxu```
2 parents a1e41a0 + 2f4b40f commit 3ec8b67

File tree

6 files changed

+229
-2
lines changed

6 files changed

+229
-2
lines changed

src/bootstrap/src/bin/main.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ fn main() {
159159
if is_bootstrap_profiling_enabled() {
160160
build.report_summary(start_time);
161161
}
162+
163+
#[cfg(feature = "tracing")]
164+
build.report_step_graph();
162165
}
163166

164167
fn check_version(config: &Config) -> Option<String> {

src/bootstrap/src/core/builder/mod.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ impl Deref for Builder<'_> {
7777
/// type's [`Debug`] implementation.
7878
///
7979
/// (Trying to debug-print `dyn Any` results in the unhelpful `"Any { .. }"`.)
80-
trait AnyDebug: Any + Debug {}
80+
pub trait AnyDebug: Any + Debug {}
8181
impl<T: Any + Debug> AnyDebug for T {}
8282
impl dyn AnyDebug {
8383
/// Equivalent to `<dyn Any>::downcast_ref`.
@@ -197,6 +197,14 @@ impl StepMetadata {
197197
// For everything else, a stage N things gets built by a stage N-1 compiler.
198198
.map(|compiler| if self.name == "std" { compiler.stage } else { compiler.stage + 1 }))
199199
}
200+
201+
pub fn get_name(&self) -> &str {
202+
&self.name
203+
}
204+
205+
pub fn get_target(&self) -> TargetSelection {
206+
self.target
207+
}
200208
}
201209

202210
pub struct RunConfig<'a> {
@@ -1657,9 +1665,24 @@ You have to build a stage1 compiler for `{}` first, and then use it to build a s
16571665
if let Some(out) = self.cache.get(&step) {
16581666
self.verbose_than(1, || println!("{}c {:?}", " ".repeat(stack.len()), step));
16591667

1668+
#[cfg(feature = "tracing")]
1669+
{
1670+
if let Some(parent) = stack.last() {
1671+
let mut graph = self.build.step_graph.borrow_mut();
1672+
graph.register_cached_step(&step, parent, self.config.dry_run());
1673+
}
1674+
}
16601675
return out;
16611676
}
16621677
self.verbose_than(1, || println!("{}> {:?}", " ".repeat(stack.len()), step));
1678+
1679+
#[cfg(feature = "tracing")]
1680+
{
1681+
let parent = stack.last();
1682+
let mut graph = self.build.step_graph.borrow_mut();
1683+
graph.register_step_execution(&step, parent, self.config.dry_run());
1684+
}
1685+
16631686
stack.push(Box::new(step.clone()));
16641687
}
16651688

src/bootstrap/src/lib.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,6 @@ pub enum GitRepo {
188188
/// although most functions are implemented as free functions rather than
189189
/// methods specifically on this structure itself (to make it easier to
190190
/// organize).
191-
#[derive(Clone)]
192191
pub struct Build {
193192
/// User-specified configuration from `bootstrap.toml`.
194193
config: Config,
@@ -244,6 +243,9 @@ pub struct Build {
244243

245244
#[cfg(feature = "build-metrics")]
246245
metrics: crate::utils::metrics::BuildMetrics,
246+
247+
#[cfg(feature = "tracing")]
248+
step_graph: std::cell::RefCell<crate::utils::step_graph::StepGraph>,
247249
}
248250

249251
#[derive(Debug, Clone)]
@@ -547,6 +549,9 @@ impl Build {
547549

548550
#[cfg(feature = "build-metrics")]
549551
metrics: crate::utils::metrics::BuildMetrics::init(),
552+
553+
#[cfg(feature = "tracing")]
554+
step_graph: std::cell::RefCell::new(crate::utils::step_graph::StepGraph::default()),
550555
};
551556

552557
// If local-rust is the same major.minor as the current version, then force a
@@ -2024,6 +2029,11 @@ to download LLVM rather than building it.
20242029
pub fn report_summary(&self, start_time: Instant) {
20252030
self.config.exec_ctx.profiler().report_summary(start_time);
20262031
}
2032+
2033+
#[cfg(feature = "tracing")]
2034+
pub fn report_step_graph(self) {
2035+
self.step_graph.into_inner().store_to_dot_files();
2036+
}
20272037
}
20282038

20292039
impl AsRef<ExecutionContext> for Build {

src/bootstrap/src/utils/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,8 @@ pub(crate) mod tracing;
1919
#[cfg(feature = "build-metrics")]
2020
pub(crate) mod metrics;
2121

22+
#[cfg(feature = "tracing")]
23+
pub(crate) mod step_graph;
24+
2225
#[cfg(test)]
2326
pub(crate) mod tests;

src/bootstrap/src/utils/step_graph.rs

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
use std::collections::{HashMap, HashSet};
2+
use std::fmt::Debug;
3+
use std::io::BufWriter;
4+
5+
use crate::core::builder::{AnyDebug, Step};
6+
7+
/// Records the executed steps and their dependencies in a directed graph,
8+
/// which can then be rendered into a DOT file for visualization.
9+
///
10+
/// The graph visualizes the first execution of a step with a solid edge,
11+
/// and cached executions of steps with a dashed edge.
12+
/// If you only want to see first executions, you can modify the code in `DotGraph` to
13+
/// always set `cached: false`.
14+
#[derive(Default)]
15+
pub struct StepGraph {
16+
/// We essentially store one graph per dry run mode.
17+
graphs: HashMap<String, DotGraph>,
18+
}
19+
20+
impl StepGraph {
21+
pub fn register_step_execution<S: Step>(
22+
&mut self,
23+
step: &S,
24+
parent: Option<&Box<dyn AnyDebug>>,
25+
dry_run: bool,
26+
) {
27+
let key = get_graph_key(dry_run);
28+
let graph = self.graphs.entry(key.to_string()).or_insert_with(|| DotGraph::default());
29+
30+
// The debug output of the step sort of serves as the unique identifier of it.
31+
// We use it to access the node ID of parents to generate edges.
32+
// We could probably also use addresses on the heap from the `Box`, but this seems less
33+
// magical.
34+
let node_key = render_step(step);
35+
36+
let label = if let Some(metadata) = step.metadata() {
37+
format!(
38+
"{}{} [{}]",
39+
metadata.get_name(),
40+
metadata.get_stage().map(|s| format!(" stage {s}")).unwrap_or_default(),
41+
metadata.get_target()
42+
)
43+
} else {
44+
let type_name = std::any::type_name::<S>();
45+
type_name
46+
.strip_prefix("bootstrap::core::")
47+
.unwrap_or(type_name)
48+
.strip_prefix("build_steps::")
49+
.unwrap_or(type_name)
50+
.to_string()
51+
};
52+
53+
let node = Node { label, tooltip: node_key.clone() };
54+
let node_handle = graph.add_node(node_key, node);
55+
56+
if let Some(parent) = parent {
57+
let parent_key = render_step(parent);
58+
if let Some(src_node_handle) = graph.get_handle_by_key(&parent_key) {
59+
graph.add_edge(src_node_handle, node_handle);
60+
}
61+
}
62+
}
63+
64+
pub fn register_cached_step<S: Step>(
65+
&mut self,
66+
step: &S,
67+
parent: &Box<dyn AnyDebug>,
68+
dry_run: bool,
69+
) {
70+
let key = get_graph_key(dry_run);
71+
let graph = self.graphs.get_mut(key).unwrap();
72+
73+
let node_key = render_step(step);
74+
let parent_key = render_step(parent);
75+
76+
if let Some(src_node_handle) = graph.get_handle_by_key(&parent_key) {
77+
if let Some(dst_node_handle) = graph.get_handle_by_key(&node_key) {
78+
graph.add_cached_edge(src_node_handle, dst_node_handle);
79+
}
80+
}
81+
}
82+
83+
pub fn store_to_dot_files(self) {
84+
for (key, graph) in self.graphs.into_iter() {
85+
let filename = format!("bootstrap-steps{key}.dot");
86+
graph.render(&filename).unwrap();
87+
}
88+
}
89+
}
90+
91+
fn get_graph_key(dry_run: bool) -> &'static str {
92+
if dry_run { ".dryrun" } else { "" }
93+
}
94+
95+
struct Node {
96+
label: String,
97+
tooltip: String,
98+
}
99+
100+
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
101+
struct NodeHandle(usize);
102+
103+
/// Represents a dependency between two bootstrap steps.
104+
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
105+
struct Edge {
106+
src: NodeHandle,
107+
dst: NodeHandle,
108+
// Was the corresponding execution of a step cached, or was the step actually executed?
109+
cached: bool,
110+
}
111+
112+
// We could use a library for this, but they either:
113+
// - require lifetimes, which gets annoying (dot_writer)
114+
// - don't support tooltips (dot_graph)
115+
// - have a lot of dependencies (graphviz_rust)
116+
// - only have SVG export (layout-rs)
117+
// - use a builder pattern that is very annoying to use here (tabbycat)
118+
#[derive(Default)]
119+
struct DotGraph {
120+
nodes: Vec<Node>,
121+
/// The `NodeHandle` represents an index within `self.nodes`
122+
edges: HashSet<Edge>,
123+
key_to_index: HashMap<String, NodeHandle>,
124+
}
125+
126+
impl DotGraph {
127+
fn add_node(&mut self, key: String, node: Node) -> NodeHandle {
128+
let handle = NodeHandle(self.nodes.len());
129+
self.nodes.push(node);
130+
self.key_to_index.insert(key, handle);
131+
handle
132+
}
133+
134+
fn add_edge(&mut self, src: NodeHandle, dst: NodeHandle) {
135+
self.edges.insert(Edge { src, dst, cached: false });
136+
}
137+
138+
fn add_cached_edge(&mut self, src: NodeHandle, dst: NodeHandle) {
139+
// There's no point in rendering both cached and uncached edge
140+
let uncached = Edge { src, dst, cached: false };
141+
if !self.edges.contains(&uncached) {
142+
self.edges.insert(Edge { src, dst, cached: true });
143+
}
144+
}
145+
146+
fn get_handle_by_key(&self, key: &str) -> Option<NodeHandle> {
147+
self.key_to_index.get(key).copied()
148+
}
149+
150+
fn render(&self, path: &str) -> std::io::Result<()> {
151+
use std::io::Write;
152+
153+
let mut file = BufWriter::new(std::fs::File::create(path)?);
154+
writeln!(file, "digraph bootstrap_steps {{")?;
155+
for (index, node) in self.nodes.iter().enumerate() {
156+
writeln!(
157+
file,
158+
r#"{index} [label="{}", tooltip="{}"]"#,
159+
escape(&node.label),
160+
escape(&node.tooltip)
161+
)?;
162+
}
163+
164+
let mut edges: Vec<&Edge> = self.edges.iter().collect();
165+
edges.sort();
166+
for edge in edges {
167+
let style = if edge.cached { "dashed" } else { "solid" };
168+
writeln!(file, r#"{} -> {} [style="{style}"]"#, edge.src.0, edge.dst.0)?;
169+
}
170+
171+
writeln!(file, "}}")
172+
}
173+
}
174+
175+
fn render_step(step: &dyn Debug) -> String {
176+
format!("{step:?}")
177+
}
178+
179+
/// Normalizes the string so that it can be rendered into a DOT file.
180+
fn escape(input: &str) -> String {
181+
input.replace("\"", "\\\"")
182+
}

src/doc/rustc-dev-guide/src/building/bootstrapping/debugging-bootstrap.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,12 @@ if [#96176][cleanup-compiler-for] is resolved.
123123

124124
[cleanup-compiler-for]: https://github.com/rust-lang/rust/issues/96176
125125

126+
### Rendering step graph
127+
128+
When you run bootstrap with the `BOOTSTRAP_TRACING` environment variable configured, bootstrap will automatically output a DOT file that shows all executed steps and their dependencies. The files will have a prefix `bootstrap-steps`. You can use e.g. `xdot` to visualize the file or e.g. `dot -Tsvg` to convert the DOT file to a SVG file.
129+
130+
A separate DOT file will be outputted for dry-run and non-dry-run execution.
131+
126132
### Using `tracing` in bootstrap
127133

128134
Both `tracing::*` macros and the `tracing::instrument` proc-macro attribute need to be gated behind `tracing` feature. Examples:

0 commit comments

Comments
 (0)