| //! This module provides linkage between libgraphviz traits and |
| //! `rustc_middle::middle::typeck::infer::region_constraints`, generating a |
| //! rendering of the graph represented by the list of `Constraint` |
| //! instances (which make up the edges of the graph), as well as the |
| //! origin for each constraint (which are attached to the labels on |
| //! each edge). |
| |
| /// For clarity, rename the graphviz crate locally to dot. |
| use graphviz as dot; |
| |
| use super::Constraint; |
| use crate::infer::region_constraints::RegionConstraintData; |
| use crate::infer::SubregionOrigin; |
| use rustc_data_structures::fx::{FxHashMap, FxHashSet}; |
| use rustc_hir::def_id::DefIndex; |
| use rustc_middle::middle::free_region::RegionRelations; |
| use rustc_middle::middle::region; |
| use rustc_middle::ty; |
| |
| use std::borrow::Cow; |
| use std::collections::btree_map::BTreeMap; |
| use std::collections::hash_map::Entry::Vacant; |
| use std::env; |
| use std::fs; |
| use std::io; |
| use std::sync::atomic::{AtomicBool, Ordering}; |
| |
| fn print_help_message() { |
| println!( |
| "\ |
| -Z print-region-graph by default prints a region constraint graph for every \n\ |
| function body, to the path `constraints.nodeXXX.dot`, where the XXX is \n\ |
| replaced with the node id of the function under analysis. \n\ |
| \n\ |
| To select one particular function body, set `RUST_REGION_GRAPH_NODE=XXX`, \n\ |
| where XXX is the node id desired. \n\ |
| \n\ |
| To generate output to some path other than the default \n\ |
| `constraints.nodeXXX.dot`, set `RUST_REGION_GRAPH=/path/desired.dot`; \n\ |
| occurrences of the character `%` in the requested path will be replaced with\n\ |
| the node id of the function under analysis. \n\ |
| \n\ |
| (Since you requested help via RUST_REGION_GRAPH=help, no region constraint \n\ |
| graphs will be printed. \n\ |
| " |
| ); |
| } |
| |
| pub fn maybe_print_constraints_for<'a, 'tcx>( |
| region_data: &RegionConstraintData<'tcx>, |
| region_rels: &RegionRelations<'a, 'tcx>, |
| ) { |
| let tcx = region_rels.tcx; |
| let context = region_rels.context; |
| |
| if !tcx.sess.opts.debugging_opts.print_region_graph { |
| return; |
| } |
| |
| let requested_node = env::var("RUST_REGION_GRAPH_NODE") |
| .ok() |
| .and_then(|s| s.parse().map(DefIndex::from_u32).ok()); |
| |
| if requested_node.is_some() && requested_node != Some(context.index) { |
| return; |
| } |
| |
| let requested_output = env::var("RUST_REGION_GRAPH"); |
| debug!("requested_output: {:?} requested_node: {:?}", requested_output, requested_node); |
| |
| let output_path = { |
| let output_template = match requested_output { |
| Ok(ref s) if s == "help" => { |
| static PRINTED_YET: AtomicBool = AtomicBool::new(false); |
| if !PRINTED_YET.load(Ordering::SeqCst) { |
| print_help_message(); |
| PRINTED_YET.store(true, Ordering::SeqCst); |
| } |
| return; |
| } |
| |
| Ok(other_path) => other_path, |
| Err(_) => "constraints.node%.dot".to_string(), |
| }; |
| |
| if output_template.is_empty() { |
| panic!("empty string provided as RUST_REGION_GRAPH"); |
| } |
| |
| if output_template.contains('%') { |
| let mut new_str = String::new(); |
| for c in output_template.chars() { |
| if c == '%' { |
| new_str.push_str(&context.index.as_u32().to_string()); |
| } else { |
| new_str.push(c); |
| } |
| } |
| new_str |
| } else { |
| output_template |
| } |
| }; |
| |
| if let Err(e) = dump_region_data_to(region_rels, ®ion_data.constraints, &output_path) { |
| let msg = format!("io error dumping region constraints: {}", e); |
| tcx.sess.err(&msg) |
| } |
| } |
| |
| struct ConstraintGraph<'a, 'tcx> { |
| graph_name: String, |
| region_rels: &'a RegionRelations<'a, 'tcx>, |
| map: &'a BTreeMap<Constraint<'tcx>, SubregionOrigin<'tcx>>, |
| node_ids: FxHashMap<Node, usize>, |
| } |
| |
| #[derive(Clone, Hash, PartialEq, Eq, Debug, Copy)] |
| enum Node { |
| RegionVid(ty::RegionVid), |
| Region(ty::RegionKind), |
| } |
| |
| #[derive(Clone, PartialEq, Eq, Debug, Copy)] |
| enum Edge<'tcx> { |
| Constraint(Constraint<'tcx>), |
| EnclScope(region::Scope, region::Scope), |
| } |
| |
| impl<'a, 'tcx> ConstraintGraph<'a, 'tcx> { |
| fn new( |
| name: String, |
| region_rels: &'a RegionRelations<'a, 'tcx>, |
| map: &'a ConstraintMap<'tcx>, |
| ) -> ConstraintGraph<'a, 'tcx> { |
| let mut i = 0; |
| let mut node_ids = FxHashMap::default(); |
| { |
| let mut add_node = |node| { |
| if let Vacant(e) = node_ids.entry(node) { |
| e.insert(i); |
| i += 1; |
| } |
| }; |
| |
| for (n1, n2) in map.keys().map(|c| constraint_to_nodes(c)) { |
| add_node(n1); |
| add_node(n2); |
| } |
| |
| region_rels.region_scope_tree.each_encl_scope(|sub, sup| { |
| add_node(Node::Region(ty::ReScope(sub))); |
| add_node(Node::Region(ty::ReScope(sup))); |
| }); |
| } |
| |
| ConstraintGraph { map, node_ids, region_rels, graph_name: name } |
| } |
| } |
| |
| impl<'a, 'tcx> dot::Labeller<'a> for ConstraintGraph<'a, 'tcx> { |
| type Node = Node; |
| type Edge = Edge<'tcx>; |
| fn graph_id(&self) -> dot::Id<'_> { |
| dot::Id::new(&*self.graph_name).unwrap() |
| } |
| fn node_id(&self, n: &Node) -> dot::Id<'_> { |
| let node_id = match self.node_ids.get(n) { |
| Some(node_id) => node_id, |
| None => bug!("no node_id found for node: {:?}", n), |
| }; |
| let name = || format!("node_{}", node_id); |
| |
| dot::Id::new(name()) |
| .unwrap_or_else(|_| bug!("failed to create graphviz node identified by {}", name())) |
| } |
| fn node_label(&self, n: &Node) -> dot::LabelText<'_> { |
| match *n { |
| Node::RegionVid(n_vid) => dot::LabelText::label(format!("{:?}", n_vid)), |
| Node::Region(n_rgn) => dot::LabelText::label(format!("{:?}", n_rgn)), |
| } |
| } |
| fn edge_label(&self, e: &Edge<'_>) -> dot::LabelText<'_> { |
| match *e { |
| Edge::Constraint(ref c) => { |
| dot::LabelText::label(format!("{:?}", self.map.get(c).unwrap())) |
| } |
| Edge::EnclScope(..) => dot::LabelText::label("(enclosed)".to_owned()), |
| } |
| } |
| } |
| |
| fn constraint_to_nodes(c: &Constraint<'_>) -> (Node, Node) { |
| match *c { |
| Constraint::VarSubVar(rv_1, rv_2) => (Node::RegionVid(rv_1), Node::RegionVid(rv_2)), |
| Constraint::RegSubVar(r_1, rv_2) => (Node::Region(*r_1), Node::RegionVid(rv_2)), |
| Constraint::VarSubReg(rv_1, r_2) => (Node::RegionVid(rv_1), Node::Region(*r_2)), |
| Constraint::RegSubReg(r_1, r_2) => (Node::Region(*r_1), Node::Region(*r_2)), |
| } |
| } |
| |
| fn edge_to_nodes(e: &Edge<'_>) -> (Node, Node) { |
| match *e { |
| Edge::Constraint(ref c) => constraint_to_nodes(c), |
| Edge::EnclScope(sub, sup) => { |
| (Node::Region(ty::ReScope(sub)), Node::Region(ty::ReScope(sup))) |
| } |
| } |
| } |
| |
| impl<'a, 'tcx> dot::GraphWalk<'a> for ConstraintGraph<'a, 'tcx> { |
| type Node = Node; |
| type Edge = Edge<'tcx>; |
| fn nodes(&self) -> dot::Nodes<'_, Node> { |
| let set = self.node_ids.keys().cloned().collect::<FxHashSet<_>>(); |
| debug!("constraint graph has {} nodes", set.len()); |
| set.into_iter().collect() |
| } |
| fn edges(&self) -> dot::Edges<'_, Edge<'tcx>> { |
| debug!("constraint graph has {} edges", self.map.len()); |
| let mut v: Vec<_> = self.map.keys().map(|e| Edge::Constraint(*e)).collect(); |
| self.region_rels |
| .region_scope_tree |
| .each_encl_scope(|sub, sup| v.push(Edge::EnclScope(sub, sup))); |
| debug!("region graph has {} edges", v.len()); |
| Cow::Owned(v) |
| } |
| fn source(&self, edge: &Edge<'tcx>) -> Node { |
| let (n1, _) = edge_to_nodes(edge); |
| debug!("edge {:?} has source {:?}", edge, n1); |
| n1 |
| } |
| fn target(&self, edge: &Edge<'tcx>) -> Node { |
| let (_, n2) = edge_to_nodes(edge); |
| debug!("edge {:?} has target {:?}", edge, n2); |
| n2 |
| } |
| } |
| |
| pub type ConstraintMap<'tcx> = BTreeMap<Constraint<'tcx>, SubregionOrigin<'tcx>>; |
| |
| fn dump_region_data_to<'a, 'tcx>( |
| region_rels: &RegionRelations<'a, 'tcx>, |
| map: &ConstraintMap<'tcx>, |
| path: &str, |
| ) -> io::Result<()> { |
| debug!("dump_region_data map (len: {}) path: {}", map.len(), path); |
| let g = ConstraintGraph::new("region_data".to_string(), region_rels, map); |
| debug!("dump_region_data calling render"); |
| let mut v = Vec::new(); |
| dot::render(&g, &mut v).unwrap(); |
| fs::write(path, &v) |
| } |