blob: 141424fc0c7819aafbdf62aaa738126eefc85c21 [file] [log] [blame]
//! This module provides linkage between libgraphviz traits and
//! `rustc_trait_selection::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, &region_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)
}