]>
Commit | Line | Data |
---|---|---|
1a4d82fc JJ |
1 | // Copyright 2014 The Rust Project Developers. See the COPYRIGHT |
2 | // file at the top-level directory of this distribution and at | |
3 | // http://rust-lang.org/COPYRIGHT. | |
4 | // | |
5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | |
6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | |
7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | |
8 | // option. This file may not be copied, modified, or distributed | |
9 | // except according to those terms. | |
10 | ||
11 | //! This module provides linkage between libgraphviz traits and | |
12 | //! `rustc::middle::typeck::infer::region_inference`, generating a | |
13 | //! rendering of the graph represented by the list of `Constraint` | |
14 | //! instances (which make up the edges of the graph), as well as the | |
15 | //! origin for each constraint (which are attached to the labels on | |
16 | //! each edge). | |
17 | ||
18 | /// For clarity, rename the graphviz crate locally to dot. | |
19 | use graphviz as dot; | |
20 | ||
21 | use middle::ty; | |
85aaf69f | 22 | use middle::region::CodeExtent; |
1a4d82fc JJ |
23 | use super::Constraint; |
24 | use middle::infer::SubregionOrigin; | |
25 | use middle::infer::region_inference::RegionVarBindings; | |
26 | use util::nodemap::{FnvHashMap, FnvHashSet}; | |
27 | use util::ppaux::Repr; | |
28 | ||
85aaf69f | 29 | use std::borrow::Cow; |
1a4d82fc | 30 | use std::collections::hash_map::Entry::Vacant; |
85aaf69f SL |
31 | use std::old_io::{self, File}; |
32 | use std::env; | |
1a4d82fc JJ |
33 | use std::sync::atomic::{AtomicBool, Ordering, ATOMIC_BOOL_INIT}; |
34 | use syntax::ast; | |
35 | ||
36 | fn print_help_message() { | |
37 | println!("\ | |
38 | -Z print-region-graph by default prints a region constraint graph for every \n\ | |
39 | function body, to the path `/tmp/constraints.nodeXXX.dot`, where the XXX is \n\ | |
40 | replaced with the node id of the function under analysis. \n\ | |
41 | \n\ | |
42 | To select one particular function body, set `RUST_REGION_GRAPH_NODE=XXX`, \n\ | |
43 | where XXX is the node id desired. \n\ | |
44 | \n\ | |
45 | To generate output to some path other than the default \n\ | |
46 | `/tmp/constraints.nodeXXX.dot`, set `RUST_REGION_GRAPH=/path/desired.dot`; \n\ | |
47 | occurrences of the character `%` in the requested path will be replaced with\n\ | |
48 | the node id of the function under analysis. \n\ | |
49 | \n\ | |
50 | (Since you requested help via RUST_REGION_GRAPH=help, no region constraint \n\ | |
51 | graphs will be printed. \n\ | |
52 | "); | |
53 | } | |
54 | ||
55 | pub fn maybe_print_constraints_for<'a, 'tcx>(region_vars: &RegionVarBindings<'a, 'tcx>, | |
56 | subject_node: ast::NodeId) { | |
57 | let tcx = region_vars.tcx; | |
58 | ||
59 | if !region_vars.tcx.sess.opts.debugging_opts.print_region_graph { | |
60 | return; | |
61 | } | |
62 | ||
63 | let requested_node : Option<ast::NodeId> = | |
85aaf69f | 64 | env::var("RUST_REGION_GRAPH_NODE").ok().and_then(|s| s.parse().ok()); |
1a4d82fc JJ |
65 | |
66 | if requested_node.is_some() && requested_node != Some(subject_node) { | |
67 | return; | |
68 | } | |
69 | ||
85aaf69f | 70 | let requested_output = env::var("RUST_REGION_GRAPH").ok(); |
1a4d82fc JJ |
71 | debug!("requested_output: {:?} requested_node: {:?}", |
72 | requested_output, requested_node); | |
73 | ||
74 | let output_path = { | |
75 | let output_template = match requested_output { | |
85aaf69f | 76 | Some(ref s) if &**s == "help" => { |
1a4d82fc JJ |
77 | static PRINTED_YET: AtomicBool = ATOMIC_BOOL_INIT; |
78 | if !PRINTED_YET.load(Ordering::SeqCst) { | |
79 | print_help_message(); | |
80 | PRINTED_YET.store(true, Ordering::SeqCst); | |
81 | } | |
82 | return; | |
83 | } | |
84 | ||
85 | Some(other_path) => other_path, | |
86 | None => "/tmp/constraints.node%.dot".to_string(), | |
87 | }; | |
88 | ||
89 | if output_template.len() == 0 { | |
90 | tcx.sess.bug("empty string provided as RUST_REGION_GRAPH"); | |
91 | } | |
92 | ||
93 | if output_template.contains_char('%') { | |
94 | let mut new_str = String::new(); | |
95 | for c in output_template.chars() { | |
96 | if c == '%' { | |
85aaf69f | 97 | new_str.push_str(&subject_node.to_string()); |
1a4d82fc JJ |
98 | } else { |
99 | new_str.push(c); | |
100 | } | |
101 | } | |
102 | new_str | |
103 | } else { | |
104 | output_template | |
105 | } | |
106 | }; | |
107 | ||
108 | let constraints = &*region_vars.constraints.borrow(); | |
85aaf69f | 109 | match dump_region_constraints_to(tcx, constraints, &output_path) { |
1a4d82fc JJ |
110 | Ok(()) => {} |
111 | Err(e) => { | |
112 | let msg = format!("io error dumping region constraints: {}", e); | |
85aaf69f | 113 | region_vars.tcx.sess.err(&msg) |
1a4d82fc JJ |
114 | } |
115 | } | |
116 | } | |
117 | ||
118 | struct ConstraintGraph<'a, 'tcx: 'a> { | |
119 | tcx: &'a ty::ctxt<'tcx>, | |
120 | graph_name: String, | |
121 | map: &'a FnvHashMap<Constraint, SubregionOrigin<'tcx>>, | |
122 | node_ids: FnvHashMap<Node, uint>, | |
123 | } | |
124 | ||
85aaf69f | 125 | #[derive(Clone, Hash, PartialEq, Eq, Debug, Copy)] |
1a4d82fc JJ |
126 | enum Node { |
127 | RegionVid(ty::RegionVid), | |
128 | Region(ty::Region), | |
129 | } | |
130 | ||
85aaf69f SL |
131 | // type Edge = Constraint; |
132 | #[derive(Clone, PartialEq, Eq, Debug, Copy)] | |
133 | enum Edge { | |
134 | Constraint(Constraint), | |
135 | EnclScope(CodeExtent, CodeExtent), | |
136 | } | |
1a4d82fc JJ |
137 | |
138 | impl<'a, 'tcx> ConstraintGraph<'a, 'tcx> { | |
139 | fn new(tcx: &'a ty::ctxt<'tcx>, | |
140 | name: String, | |
141 | map: &'a ConstraintMap<'tcx>) -> ConstraintGraph<'a, 'tcx> { | |
142 | let mut i = 0; | |
85aaf69f | 143 | let mut node_ids = FnvHashMap(); |
1a4d82fc | 144 | { |
85aaf69f | 145 | let mut add_node = |node| { |
1a4d82fc JJ |
146 | if let Vacant(e) = node_ids.entry(node) { |
147 | e.insert(i); | |
148 | i += 1; | |
149 | } | |
150 | }; | |
151 | ||
152 | for (n1, n2) in map.keys().map(|c|constraint_to_nodes(c)) { | |
153 | add_node(n1); | |
154 | add_node(n2); | |
155 | } | |
85aaf69f SL |
156 | |
157 | tcx.region_maps.each_encl_scope(|sub, sup| { | |
158 | add_node(Node::Region(ty::ReScope(*sub))); | |
159 | add_node(Node::Region(ty::ReScope(*sup))); | |
160 | }); | |
1a4d82fc JJ |
161 | } |
162 | ||
163 | ConstraintGraph { tcx: tcx, | |
164 | graph_name: name, | |
165 | map: map, | |
166 | node_ids: node_ids } | |
167 | } | |
168 | } | |
169 | ||
170 | impl<'a, 'tcx> dot::Labeller<'a, Node, Edge> for ConstraintGraph<'a, 'tcx> { | |
171 | fn graph_id(&self) -> dot::Id { | |
85aaf69f | 172 | dot::Id::new(&*self.graph_name).ok().unwrap() |
1a4d82fc JJ |
173 | } |
174 | fn node_id(&self, n: &Node) -> dot::Id { | |
85aaf69f SL |
175 | let node_id = match self.node_ids.get(n) { |
176 | Some(node_id) => node_id, | |
177 | None => panic!("no node_id found for node: {:?}", n), | |
178 | }; | |
179 | let name = || format!("node_{}", node_id); | |
180 | match dot::Id::new(name()) { | |
181 | Ok(id) => id, | |
182 | Err(_) => { | |
183 | panic!("failed to create graphviz node identified by {}", name()); | |
184 | } | |
185 | } | |
1a4d82fc JJ |
186 | } |
187 | fn node_label(&self, n: &Node) -> dot::LabelText { | |
188 | match *n { | |
189 | Node::RegionVid(n_vid) => | |
190 | dot::LabelText::label(format!("{:?}", n_vid)), | |
191 | Node::Region(n_rgn) => | |
192 | dot::LabelText::label(format!("{}", n_rgn.repr(self.tcx))), | |
193 | } | |
194 | } | |
195 | fn edge_label(&self, e: &Edge) -> dot::LabelText { | |
85aaf69f SL |
196 | match *e { |
197 | Edge::Constraint(ref c) => | |
198 | dot::LabelText::label(format!("{}", self.map.get(c).unwrap().repr(self.tcx))), | |
199 | Edge::EnclScope(..) => | |
200 | dot::LabelText::label(format!("(enclosed)")), | |
201 | } | |
1a4d82fc JJ |
202 | } |
203 | } | |
204 | ||
205 | fn constraint_to_nodes(c: &Constraint) -> (Node, Node) { | |
206 | match *c { | |
207 | Constraint::ConstrainVarSubVar(rv_1, rv_2) => (Node::RegionVid(rv_1), | |
208 | Node::RegionVid(rv_2)), | |
209 | Constraint::ConstrainRegSubVar(r_1, rv_2) => (Node::Region(r_1), | |
210 | Node::RegionVid(rv_2)), | |
211 | Constraint::ConstrainVarSubReg(rv_1, r_2) => (Node::RegionVid(rv_1), | |
212 | Node::Region(r_2)), | |
213 | } | |
214 | } | |
215 | ||
85aaf69f SL |
216 | fn edge_to_nodes(e: &Edge) -> (Node, Node) { |
217 | match *e { | |
218 | Edge::Constraint(ref c) => constraint_to_nodes(c), | |
219 | Edge::EnclScope(sub, sup) => { | |
220 | (Node::Region(ty::ReScope(sub)), Node::Region(ty::ReScope(sup))) | |
221 | } | |
222 | } | |
223 | } | |
224 | ||
1a4d82fc JJ |
225 | impl<'a, 'tcx> dot::GraphWalk<'a, Node, Edge> for ConstraintGraph<'a, 'tcx> { |
226 | fn nodes(&self) -> dot::Nodes<Node> { | |
85aaf69f SL |
227 | let mut set = FnvHashSet(); |
228 | for node in self.node_ids.keys() { | |
229 | set.insert(*node); | |
1a4d82fc JJ |
230 | } |
231 | debug!("constraint graph has {} nodes", set.len()); | |
232 | set.into_iter().collect() | |
233 | } | |
234 | fn edges(&self) -> dot::Edges<Edge> { | |
235 | debug!("constraint graph has {} edges", self.map.len()); | |
85aaf69f SL |
236 | let mut v : Vec<_> = self.map.keys().map(|e| Edge::Constraint(*e)).collect(); |
237 | self.tcx.region_maps.each_encl_scope(|sub, sup| { | |
238 | v.push(Edge::EnclScope(*sub, *sup)) | |
239 | }); | |
240 | debug!("region graph has {} edges", v.len()); | |
241 | Cow::Owned(v) | |
1a4d82fc JJ |
242 | } |
243 | fn source(&self, edge: &Edge) -> Node { | |
85aaf69f | 244 | let (n1, _) = edge_to_nodes(edge); |
1a4d82fc JJ |
245 | debug!("edge {:?} has source {:?}", edge, n1); |
246 | n1 | |
247 | } | |
248 | fn target(&self, edge: &Edge) -> Node { | |
85aaf69f | 249 | let (_, n2) = edge_to_nodes(edge); |
1a4d82fc JJ |
250 | debug!("edge {:?} has target {:?}", edge, n2); |
251 | n2 | |
252 | } | |
253 | } | |
254 | ||
255 | pub type ConstraintMap<'tcx> = FnvHashMap<Constraint, SubregionOrigin<'tcx>>; | |
256 | ||
257 | fn dump_region_constraints_to<'a, 'tcx:'a >(tcx: &'a ty::ctxt<'tcx>, | |
258 | map: &ConstraintMap<'tcx>, | |
85aaf69f | 259 | path: &str) -> old_io::IoResult<()> { |
1a4d82fc JJ |
260 | debug!("dump_region_constraints map (len: {}) path: {}", map.len(), path); |
261 | let g = ConstraintGraph::new(tcx, format!("region_constraints"), map); | |
262 | let mut f = File::create(&Path::new(path)); | |
263 | debug!("dump_region_constraints calling render"); | |
264 | dot::render(&g, &mut f) | |
265 | } |