Skip to content

Commit 2f91ba0

Browse files
committed
implement a debugging "shadow graph"
The shadow graph supercedes the existing code that checked for reads/writes without an active task and now adds the ability to filter for specific edges.
1 parent 4c2f3ff commit 2f91ba0

File tree

6 files changed

+176
-28
lines changed

6 files changed

+176
-28
lines changed

src/librustc/dep_graph/README.md

+31
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,8 @@ path is found (as demonstrated above).
341341

342342
### Debugging the dependency graph
343343

344+
#### Dumping the graph
345+
344346
The compiler is also capable of dumping the dependency graph for your
345347
debugging pleasure. To do so, pass the `-Z dump-dep-graph` flag. The
346348
graph will be dumped to `dep_graph.{txt,dot}` in the current
@@ -392,6 +394,35 @@ This will dump out all the nodes that lead from `Hir(foo)` to
392394
`TypeckItemBody(bar)`, from which you can (hopefully) see the source
393395
of the erroneous edge.
394396

397+
#### Tracking down incorrect edges
398+
399+
Sometimes, after you dump the dependency graph, you will find some
400+
path that should not exist, but you will not be quite sure how it came
401+
to be. **When the compiler is built with debug assertions,** it can
402+
help you track that down. Simply set the `RUST_FORBID_DEP_GRAPH_EDGE`
403+
environment variable to a filter. Every edge created in the dep-graph
404+
will be tested against that filter -- if it matches, a `bug!` is
405+
reported, so you can easily see the backtrace (`RUST_BACKTRACE=1`).
406+
407+
The syntax for these filters is the same as described in the previous
408+
section. However, note that this filter is applied to every **edge**
409+
and doesn't handle longer paths in the graph, unlike the previous
410+
section.
411+
412+
Example:
413+
414+
You find that there is a path from the `Hir` of `foo` to the type
415+
check of `bar` and you don't think there should be. You dump the
416+
dep-graph as described in the previous section and open `dep-graph.txt`
417+
to see something like:
418+
419+
Hir(foo) -> Collect(bar)
420+
Collect(bar) -> TypeckItemBody(bar)
421+
422+
That first edge looks suspicious to you. So you set
423+
`RUST_FORBID_DEP_GRAPH_EDGE` to `Hir&foo -> Collect&bar`, re-run, and
424+
then observe the backtrace. Voila, bug fixed!
425+
395426
### Inlining of HIR nodes
396427

397428
For the time being, at least, we still sometimes "inline" HIR nodes

src/librustc/dep_graph/debug.rs

+7
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,11 @@ impl EdgeFilter {
6666
})
6767
}
6868
}
69+
70+
pub fn test<D: Clone + Debug>(&self,
71+
source: &DepNode<D>,
72+
target: &DepNode<D>)
73+
-> bool {
74+
self.source.test(source) && self.target.test(target)
75+
}
6976
}

src/librustc/dep_graph/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ mod edges;
1515
mod graph;
1616
mod query;
1717
mod raii;
18+
mod shadow;
1819
mod thread;
1920
mod visit;
2021

src/librustc/dep_graph/shadow.rs

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
//! The "Shadow Graph" is maintained on the main thread and which
2+
//! tracks each message relating to the dep-graph and applies some
3+
//! sanity checks as they go by. If an error results, it means you get
4+
//! a nice stack-trace telling you precisely what caused the error.
5+
//!
6+
//! NOTE: This is a debugging facility which can potentially have non-trivial
7+
//! runtime impact. Therefore, it is largely compiled out if
8+
//! debug-assertions are not enabled.
9+
//!
10+
//! The basic sanity check, always enabled, is that there is always a
11+
//! task (or ignore) on the stack when you do read/write.
12+
//!
13+
//! Optionally, if you specify RUST_FORBID_DEP_GRAPH_EDGE, you can
14+
//! specify an edge filter to be applied to each edge as it is
15+
//! created. See `./README.md` for details.
16+
17+
use hir::def_id::DefId;
18+
use std::cell::{BorrowState, RefCell};
19+
use std::env;
20+
21+
use super::DepNode;
22+
use super::thread::DepMessage;
23+
use super::debug::EdgeFilter;
24+
25+
pub struct ShadowGraph {
26+
// if you push None onto the stack, that corresponds to an Ignore
27+
stack: RefCell<Vec<Option<DepNode<DefId>>>>,
28+
forbidden_edge: Option<EdgeFilter>,
29+
}
30+
31+
const ENABLED: bool = cfg!(debug_assertions);
32+
33+
impl ShadowGraph {
34+
pub fn new() -> Self {
35+
let forbidden_edge = if !ENABLED {
36+
None
37+
} else {
38+
match env::var("RUST_FORBID_DEP_GRAPH_EDGE") {
39+
Ok(s) => {
40+
match EdgeFilter::new(&s) {
41+
Ok(f) => Some(f),
42+
Err(err) => bug!("RUST_FORBID_DEP_GRAPH_EDGE invalid: {}", err),
43+
}
44+
}
45+
Err(_) => None,
46+
}
47+
};
48+
49+
ShadowGraph {
50+
stack: RefCell::new(vec![]),
51+
forbidden_edge: forbidden_edge,
52+
}
53+
}
54+
55+
pub fn enqueue(&self, message: &DepMessage) {
56+
if ENABLED {
57+
match self.stack.borrow_state() {
58+
BorrowState::Unused => {}
59+
_ => {
60+
// When we apply edge filters, that invokes the
61+
// Debug trait on DefIds, which in turn reads from
62+
// various bits of state and creates reads! Ignore
63+
// those recursive reads.
64+
return;
65+
}
66+
}
67+
68+
let mut stack = self.stack.borrow_mut();
69+
match *message {
70+
DepMessage::Read(ref n) => self.check_edge(Some(Some(n)), top(&stack)),
71+
DepMessage::Write(ref n) => self.check_edge(top(&stack), Some(Some(n))),
72+
DepMessage::PushTask(ref n) => stack.push(Some(n.clone())),
73+
DepMessage::PushIgnore => stack.push(None),
74+
DepMessage::PopTask(_) |
75+
DepMessage::PopIgnore => {
76+
// we could easily check that the stack is
77+
// well-formed here, but since we use closures and
78+
// RAII accessors, this bug basically never
79+
// happens, so it seems not worth the overhead
80+
stack.pop();
81+
}
82+
DepMessage::Query => (),
83+
}
84+
}
85+
}
86+
87+
fn check_edge(&self,
88+
source: Option<Option<&DepNode<DefId>>>,
89+
target: Option<Option<&DepNode<DefId>>>) {
90+
assert!(ENABLED);
91+
match (source, target) {
92+
// cannot happen, one side is always Some(Some(_))
93+
(None, None) => unreachable!(),
94+
95+
// nothing on top of the stack
96+
(None, Some(n)) | (Some(n), None) => bug!("read/write of {:?} but no current task", n),
97+
98+
// this corresponds to an Ignore being top of the stack
99+
(Some(None), _) | (_, Some(None)) => (),
100+
101+
// a task is on top of the stack
102+
(Some(Some(source)), Some(Some(target))) => {
103+
if let Some(ref forbidden_edge) = self.forbidden_edge {
104+
if forbidden_edge.test(source, target) {
105+
bug!("forbidden edge {:?} -> {:?} created", source, target)
106+
}
107+
}
108+
}
109+
}
110+
}
111+
}
112+
113+
// Do a little juggling: we get back a reference to an option at the
114+
// top of the stack, convert it to an optional reference.
115+
fn top<'s>(stack: &'s Vec<Option<DepNode<DefId>>>) -> Option<Option<&'s DepNode<DefId>>> {
116+
stack.last()
117+
.map(|n: &'s Option<DepNode<DefId>>| -> Option<&'s DepNode<DefId>> {
118+
// (*)
119+
// (*) type annotation just there to clarify what would
120+
// otherwise be some *really* obscure code
121+
n.as_ref()
122+
})
123+
}

src/librustc/dep_graph/thread.rs

+13-28
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@
2020
2121
use hir::def_id::DefId;
2222
use rustc_data_structures::veccell::VecCell;
23-
use std::cell::Cell;
2423
use std::sync::mpsc::{self, Sender, Receiver};
2524
use std::thread;
2625

2726
use super::DepGraphQuery;
2827
use super::DepNode;
2928
use super::edges::DepGraphEdges;
29+
use super::shadow::ShadowGraph;
3030

3131
#[derive(Debug)]
3232
pub enum DepMessage {
@@ -42,12 +42,16 @@ pub enum DepMessage {
4242
pub struct DepGraphThreadData {
4343
enabled: bool,
4444

45-
// Local counter that just tracks how many tasks are pushed onto the
46-
// stack, so that we still get an error in the case where one is
47-
// missing. If dep-graph construction is enabled, we'd get the same
48-
// error when processing tasks later on, but that's annoying because
49-
// it lacks precision about the source of the error.
50-
tasks_pushed: Cell<usize>,
45+
// The "shadow graph" is a debugging aid. We give it each message
46+
// in real time as it arrives and it checks for various errors
47+
// (for example, a read/write when there is no current task; it
48+
// can also apply user-defined filters; see `shadow` module for
49+
// details). This only occurs if debug-assertions are enabled.
50+
//
51+
// Note that in some cases the same errors will occur when the
52+
// data is processed off the main thread, but that's annoying
53+
// because it lacks precision about the source of the error.
54+
shadow_graph: ShadowGraph,
5155

5256
// current buffer, where we accumulate messages
5357
messages: VecCell<DepMessage>,
@@ -76,7 +80,7 @@ impl DepGraphThreadData {
7680

7781
DepGraphThreadData {
7882
enabled: enabled,
79-
tasks_pushed: Cell::new(0),
83+
shadow_graph: ShadowGraph::new(),
8084
messages: VecCell::with_capacity(INITIAL_CAPACITY),
8185
swap_in: rx2,
8286
swap_out: tx1,
@@ -118,21 +122,7 @@ impl DepGraphThreadData {
118122
/// the buffer is full, this may swap.)
119123
#[inline]
120124
pub fn enqueue(&self, message: DepMessage) {
121-
// Regardless of whether dep graph construction is enabled, we
122-
// still want to check that we always have a valid task on the
123-
// stack when a read/write/etc event occurs.
124-
match message {
125-
DepMessage::Read(_) | DepMessage::Write(_) =>
126-
if self.tasks_pushed.get() == 0 {
127-
self.invalid_message("read/write but no current task")
128-
},
129-
DepMessage::PushTask(_) | DepMessage::PushIgnore =>
130-
self.tasks_pushed.set(self.tasks_pushed.get() + 1),
131-
DepMessage::PopTask(_) | DepMessage::PopIgnore =>
132-
self.tasks_pushed.set(self.tasks_pushed.get() - 1),
133-
DepMessage::Query =>
134-
(),
135-
}
125+
self.shadow_graph.enqueue(&message);
136126

137127
if self.enabled {
138128
self.enqueue_enabled(message);
@@ -147,11 +137,6 @@ impl DepGraphThreadData {
147137
self.swap();
148138
}
149139
}
150-
151-
// Outline this too.
152-
fn invalid_message(&self, string: &str) {
153-
bug!("{}; see src/librustc/dep_graph/README.md for more information", string)
154-
}
155140
}
156141

157142
/// Definition of the depgraph thread.

src/librustc/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#![cfg_attr(not(stage0), deny(warnings))]
2525

2626
#![feature(associated_consts)]
27+
#![feature(borrow_state)]
2728
#![feature(box_patterns)]
2829
#![feature(box_syntax)]
2930
#![feature(collections)]

0 commit comments

Comments
 (0)