Move the dataflow framework to its own crate.
This commit is contained in:
parent
81a600b6b7
commit
fd9c04fe32
74 changed files with 259 additions and 211 deletions
|
@ -51,6 +51,8 @@
|
|||
#![feature(associated_type_defaults)]
|
||||
#![feature(iter_zip)]
|
||||
#![feature(thread_local_const_init)]
|
||||
#![feature(trusted_step)]
|
||||
#![feature(try_blocks)]
|
||||
#![feature(try_reserve)]
|
||||
#![feature(try_reserve_kind)]
|
||||
#![feature(nonzero_ops)]
|
||||
|
|
69
compiler/rustc_middle/src/mir/generic_graph.rs
Normal file
69
compiler/rustc_middle/src/mir/generic_graph.rs
Normal file
|
@ -0,0 +1,69 @@
|
|||
use gsgdt::{Edge, Graph, Node, NodeStyle};
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_middle::mir::*;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
|
||||
/// Convert an MIR function into a gsgdt Graph
|
||||
pub fn mir_fn_to_generic_graph<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'_>) -> Graph {
|
||||
let def_id = body.source.def_id();
|
||||
let def_name = graphviz_safe_def_name(def_id);
|
||||
let graph_name = format!("Mir_{}", def_name);
|
||||
let dark_mode = tcx.sess.opts.debugging_opts.graphviz_dark_mode;
|
||||
|
||||
// Nodes
|
||||
let nodes: Vec<Node> = body
|
||||
.basic_blocks()
|
||||
.iter_enumerated()
|
||||
.map(|(block, _)| bb_to_graph_node(block, body, dark_mode))
|
||||
.collect();
|
||||
|
||||
// Edges
|
||||
let mut edges = Vec::new();
|
||||
for (source, _) in body.basic_blocks().iter_enumerated() {
|
||||
let def_id = body.source.def_id();
|
||||
let terminator = body[source].terminator();
|
||||
let labels = terminator.kind.fmt_successor_labels();
|
||||
|
||||
for (&target, label) in terminator.successors().zip(labels) {
|
||||
let src = node(def_id, source);
|
||||
let trg = node(def_id, target);
|
||||
edges.push(Edge::new(src, trg, label.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
Graph::new(graph_name, nodes, edges)
|
||||
}
|
||||
|
||||
fn bb_to_graph_node(block: BasicBlock, body: &Body<'_>, dark_mode: bool) -> Node {
|
||||
let def_id = body.source.def_id();
|
||||
let data = &body[block];
|
||||
let label = node(def_id, block);
|
||||
|
||||
let (title, bgcolor) = if data.is_cleanup {
|
||||
let color = if dark_mode { "royalblue" } else { "lightblue" };
|
||||
(format!("{} (cleanup)", block.index()), color)
|
||||
} else {
|
||||
let color = if dark_mode { "dimgray" } else { "gray" };
|
||||
(format!("{}", block.index()), color)
|
||||
};
|
||||
|
||||
let style = NodeStyle { title_bg: Some(bgcolor.to_owned()), ..Default::default() };
|
||||
let mut stmts: Vec<String> = data.statements.iter().map(|x| format!("{:?}", x)).collect();
|
||||
|
||||
// add the terminator to the stmts, gsgdt can print it out seperately
|
||||
let mut terminator_head = String::new();
|
||||
data.terminator().kind.fmt_head(&mut terminator_head).unwrap();
|
||||
stmts.push(terminator_head);
|
||||
|
||||
Node::new(stmts, label, title, style)
|
||||
}
|
||||
|
||||
// Must match `[0-9A-Za-z_]*`. This does not appear in the rendered graph, so
|
||||
// it does not have to be user friendly.
|
||||
pub fn graphviz_safe_def_name(def_id: DefId) -> String {
|
||||
format!("{}_{}", def_id.krate.index(), def_id.index.index(),)
|
||||
}
|
||||
|
||||
fn node(def_id: DefId, block: BasicBlock) -> String {
|
||||
format!("bb{}__{}", block.index(), graphviz_safe_def_name(def_id))
|
||||
}
|
173
compiler/rustc_middle/src/mir/generic_graphviz.rs
Normal file
173
compiler/rustc_middle/src/mir/generic_graphviz.rs
Normal file
|
@ -0,0 +1,173 @@
|
|||
use rustc_data_structures::graph::{self, iterate};
|
||||
use rustc_graphviz as dot;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use std::io::{self, Write};
|
||||
|
||||
pub struct GraphvizWriter<
|
||||
'a,
|
||||
G: graph::DirectedGraph + graph::WithSuccessors + graph::WithStartNode + graph::WithNumNodes,
|
||||
NodeContentFn: Fn(<G as graph::DirectedGraph>::Node) -> Vec<String>,
|
||||
EdgeLabelsFn: Fn(<G as graph::DirectedGraph>::Node) -> Vec<String>,
|
||||
> {
|
||||
graph: &'a G,
|
||||
is_subgraph: bool,
|
||||
graphviz_name: String,
|
||||
graph_label: Option<String>,
|
||||
node_content_fn: NodeContentFn,
|
||||
edge_labels_fn: EdgeLabelsFn,
|
||||
}
|
||||
|
||||
impl<
|
||||
'a,
|
||||
G: graph::DirectedGraph + graph::WithSuccessors + graph::WithStartNode + graph::WithNumNodes,
|
||||
NodeContentFn: Fn(<G as graph::DirectedGraph>::Node) -> Vec<String>,
|
||||
EdgeLabelsFn: Fn(<G as graph::DirectedGraph>::Node) -> Vec<String>,
|
||||
> GraphvizWriter<'a, G, NodeContentFn, EdgeLabelsFn>
|
||||
{
|
||||
pub fn new(
|
||||
graph: &'a G,
|
||||
graphviz_name: &str,
|
||||
node_content_fn: NodeContentFn,
|
||||
edge_labels_fn: EdgeLabelsFn,
|
||||
) -> Self {
|
||||
Self {
|
||||
graph,
|
||||
is_subgraph: false,
|
||||
graphviz_name: graphviz_name.to_owned(),
|
||||
graph_label: None,
|
||||
node_content_fn,
|
||||
edge_labels_fn,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_graph_label(&mut self, graph_label: &str) {
|
||||
self.graph_label = Some(graph_label.to_owned());
|
||||
}
|
||||
|
||||
/// Write a graphviz DOT of the graph
|
||||
pub fn write_graphviz<'tcx, W>(&self, tcx: TyCtxt<'tcx>, w: &mut W) -> io::Result<()>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
let kind = if self.is_subgraph { "subgraph" } else { "digraph" };
|
||||
let cluster = if self.is_subgraph { "cluster_" } else { "" }; // Print border around graph
|
||||
// FIXME(richkadel): If/when migrating the MIR graphviz to this generic implementation,
|
||||
// prepend "Mir_" to the graphviz_safe_def_name(def_id)
|
||||
writeln!(w, "{} {}{} {{", kind, cluster, self.graphviz_name)?;
|
||||
|
||||
// Global graph properties
|
||||
let font = format!(r#"fontname="{}""#, tcx.sess.opts.debugging_opts.graphviz_font);
|
||||
let mut graph_attrs = vec![&font[..]];
|
||||
let mut content_attrs = vec![&font[..]];
|
||||
|
||||
let dark_mode = tcx.sess.opts.debugging_opts.graphviz_dark_mode;
|
||||
if dark_mode {
|
||||
graph_attrs.push(r#"bgcolor="black""#);
|
||||
graph_attrs.push(r#"fontcolor="white""#);
|
||||
content_attrs.push(r#"color="white""#);
|
||||
content_attrs.push(r#"fontcolor="white""#);
|
||||
}
|
||||
|
||||
writeln!(w, r#" graph [{}];"#, graph_attrs.join(" "))?;
|
||||
let content_attrs_str = content_attrs.join(" ");
|
||||
writeln!(w, r#" node [{}];"#, content_attrs_str)?;
|
||||
writeln!(w, r#" edge [{}];"#, content_attrs_str)?;
|
||||
|
||||
// Graph label
|
||||
if let Some(graph_label) = &self.graph_label {
|
||||
self.write_graph_label(graph_label, w)?;
|
||||
}
|
||||
|
||||
// Nodes
|
||||
for node in iterate::post_order_from(self.graph, self.graph.start_node()) {
|
||||
self.write_node(node, dark_mode, w)?;
|
||||
}
|
||||
|
||||
// Edges
|
||||
for source in iterate::post_order_from(self.graph, self.graph.start_node()) {
|
||||
self.write_edges(source, w)?;
|
||||
}
|
||||
writeln!(w, "}}")
|
||||
}
|
||||
|
||||
/// Write a graphviz DOT node for the given node.
|
||||
pub fn write_node<W>(&self, node: G::Node, dark_mode: bool, w: &mut W) -> io::Result<()>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
// Start a new node with the label to follow, in one of DOT's pseudo-HTML tables.
|
||||
write!(w, r#" {} [shape="none", label=<"#, self.node(node))?;
|
||||
|
||||
write!(w, r#"<table border="0" cellborder="1" cellspacing="0">"#)?;
|
||||
|
||||
// FIXME(richkadel): If/when migrating the MIR graphviz to this generic implementation,
|
||||
// we need generic way to know if node header should have a different color. For example,
|
||||
// for MIR:
|
||||
//
|
||||
// let (blk, bgcolor) = if data.is_cleanup {
|
||||
// let color = if dark_mode { "royalblue" } else { "lightblue" };
|
||||
// (format!("{:?} (cleanup)", node), color)
|
||||
// } else {
|
||||
// let color = if dark_mode { "dimgray" } else { "gray" };
|
||||
// (format!("{:?}", node), color)
|
||||
// };
|
||||
let color = if dark_mode { "dimgray" } else { "gray" };
|
||||
let (blk, bgcolor) = (format!("{:?}", node), color);
|
||||
write!(
|
||||
w,
|
||||
r#"<tr><td bgcolor="{bgcolor}" {attrs} colspan="{colspan}">{blk}</td></tr>"#,
|
||||
attrs = r#"align="center""#,
|
||||
colspan = 1,
|
||||
blk = blk,
|
||||
bgcolor = bgcolor
|
||||
)?;
|
||||
|
||||
for section in (self.node_content_fn)(node) {
|
||||
write!(
|
||||
w,
|
||||
r#"<tr><td align="left" balign="left">{}</td></tr>"#,
|
||||
dot::escape_html(§ion).replace("\n", "<br/>")
|
||||
)?;
|
||||
}
|
||||
|
||||
// Close the table
|
||||
write!(w, "</table>")?;
|
||||
|
||||
// Close the node label and the node itself.
|
||||
writeln!(w, ">];")
|
||||
}
|
||||
|
||||
/// Write graphviz DOT edges with labels between the given node and all of its successors.
|
||||
fn write_edges<W>(&self, source: G::Node, w: &mut W) -> io::Result<()>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
let edge_labels = (self.edge_labels_fn)(source);
|
||||
for (index, target) in self.graph.successors(source).enumerate() {
|
||||
let src = self.node(source);
|
||||
let trg = self.node(target);
|
||||
let escaped_edge_label = if let Some(edge_label) = edge_labels.get(index) {
|
||||
dot::escape_html(edge_label).replace("\n", r#"<br align="left"/>"#)
|
||||
} else {
|
||||
"".to_owned()
|
||||
};
|
||||
writeln!(w, r#" {} -> {} [label=<{}>];"#, src, trg, escaped_edge_label)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write the graphviz DOT label for the overall graph. This is essentially a block of text that
|
||||
/// will appear below the graph.
|
||||
fn write_graph_label<W>(&self, label: &str, w: &mut W) -> io::Result<()>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
let lines = label.split('\n').map(|s| dot::escape_html(s)).collect::<Vec<_>>();
|
||||
let escaped_label = lines.join(r#"<br align="left"/>"#);
|
||||
writeln!(w, r#" label=<<br/><br/>{}<br align="left"/><br/><br/><br/>>;"#, escaped_label)
|
||||
}
|
||||
|
||||
fn node(&self, node: G::Node) -> String {
|
||||
format!("{:?}__{}", node, self.graphviz_name)
|
||||
}
|
||||
}
|
134
compiler/rustc_middle/src/mir/graphviz.rs
Normal file
134
compiler/rustc_middle/src/mir/graphviz.rs
Normal file
|
@ -0,0 +1,134 @@
|
|||
use gsgdt::GraphvizSettings;
|
||||
use rustc_graphviz as dot;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_middle::mir::*;
|
||||
use rustc_middle::ty::{self, TyCtxt};
|
||||
use std::fmt::Debug;
|
||||
use std::io::{self, Write};
|
||||
|
||||
use super::generic_graph::mir_fn_to_generic_graph;
|
||||
use super::pretty::dump_mir_def_ids;
|
||||
|
||||
/// Write a graphviz DOT graph of a list of MIRs.
|
||||
pub fn write_mir_graphviz<W>(tcx: TyCtxt<'_>, single: Option<DefId>, w: &mut W) -> io::Result<()>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
let def_ids = dump_mir_def_ids(tcx, single);
|
||||
|
||||
let mirs =
|
||||
def_ids
|
||||
.iter()
|
||||
.flat_map(|def_id| {
|
||||
if tcx.is_const_fn_raw(*def_id) {
|
||||
vec![tcx.optimized_mir(*def_id), tcx.mir_for_ctfe(*def_id)]
|
||||
} else {
|
||||
vec![tcx.instance_mir(ty::InstanceDef::Item(ty::WithOptConstParam::unknown(
|
||||
*def_id,
|
||||
)))]
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let use_subgraphs = mirs.len() > 1;
|
||||
if use_subgraphs {
|
||||
writeln!(w, "digraph __crate__ {{")?;
|
||||
}
|
||||
|
||||
for mir in mirs {
|
||||
write_mir_fn_graphviz(tcx, mir, use_subgraphs, w)?;
|
||||
}
|
||||
|
||||
if use_subgraphs {
|
||||
writeln!(w, "}}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write a graphviz DOT graph of the MIR.
|
||||
pub fn write_mir_fn_graphviz<'tcx, W>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
body: &Body<'_>,
|
||||
subgraph: bool,
|
||||
w: &mut W,
|
||||
) -> io::Result<()>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
// Global graph properties
|
||||
let font = format!(r#"fontname="{}""#, tcx.sess.opts.debugging_opts.graphviz_font);
|
||||
let mut graph_attrs = vec![&font[..]];
|
||||
let mut content_attrs = vec![&font[..]];
|
||||
|
||||
let dark_mode = tcx.sess.opts.debugging_opts.graphviz_dark_mode;
|
||||
if dark_mode {
|
||||
graph_attrs.push(r#"bgcolor="black""#);
|
||||
graph_attrs.push(r#"fontcolor="white""#);
|
||||
content_attrs.push(r#"color="white""#);
|
||||
content_attrs.push(r#"fontcolor="white""#);
|
||||
}
|
||||
|
||||
// Graph label
|
||||
let mut label = String::from("");
|
||||
// FIXME: remove this unwrap
|
||||
write_graph_label(tcx, body, &mut label).unwrap();
|
||||
let g = mir_fn_to_generic_graph(tcx, body);
|
||||
let settings = GraphvizSettings {
|
||||
graph_attrs: Some(graph_attrs.join(" ")),
|
||||
node_attrs: Some(content_attrs.join(" ")),
|
||||
edge_attrs: Some(content_attrs.join(" ")),
|
||||
graph_label: Some(label),
|
||||
};
|
||||
g.to_dot(w, &settings, subgraph)
|
||||
}
|
||||
|
||||
/// Write the graphviz DOT label for the overall graph. This is essentially a block of text that
|
||||
/// will appear below the graph, showing the type of the `fn` this MIR represents and the types of
|
||||
/// all the variables and temporaries.
|
||||
fn write_graph_label<'tcx, W: std::fmt::Write>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
body: &Body<'_>,
|
||||
w: &mut W,
|
||||
) -> std::fmt::Result {
|
||||
let def_id = body.source.def_id();
|
||||
|
||||
write!(w, "fn {}(", dot::escape_html(&tcx.def_path_str(def_id)))?;
|
||||
|
||||
// fn argument types.
|
||||
for (i, arg) in body.args_iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(w, ", ")?;
|
||||
}
|
||||
write!(w, "{:?}: {}", Place::from(arg), escape(&body.local_decls[arg].ty))?;
|
||||
}
|
||||
|
||||
write!(w, ") -> {}", escape(&body.return_ty()))?;
|
||||
write!(w, r#"<br align="left"/>"#)?;
|
||||
|
||||
for local in body.vars_and_temps_iter() {
|
||||
let decl = &body.local_decls[local];
|
||||
|
||||
write!(w, "let ")?;
|
||||
if decl.mutability == Mutability::Mut {
|
||||
write!(w, "mut ")?;
|
||||
}
|
||||
|
||||
write!(w, r#"{:?}: {};<br align="left"/>"#, Place::from(local), escape(&decl.ty))?;
|
||||
}
|
||||
|
||||
for var_debug_info in &body.var_debug_info {
|
||||
write!(
|
||||
w,
|
||||
r#"debug {} => {};<br align="left"/>"#,
|
||||
var_debug_info.name,
|
||||
escape(&var_debug_info.value),
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn escape<T: Debug>(t: &T) -> String {
|
||||
dot::escape_html(&format!("{:?}", t))
|
||||
}
|
|
@ -42,11 +42,17 @@ pub use self::query::*;
|
|||
|
||||
pub mod abstract_const;
|
||||
pub mod coverage;
|
||||
mod generic_graph;
|
||||
pub mod generic_graphviz;
|
||||
mod graph_cyclic_cache;
|
||||
pub mod graphviz;
|
||||
pub mod interpret;
|
||||
pub mod mono;
|
||||
pub mod patch;
|
||||
mod predecessors;
|
||||
pub mod pretty;
|
||||
mod query;
|
||||
pub mod spanview;
|
||||
pub mod tcx;
|
||||
pub mod terminator;
|
||||
pub use terminator::*;
|
||||
|
@ -54,6 +60,12 @@ pub mod traversal;
|
|||
mod type_foldable;
|
||||
pub mod visit;
|
||||
|
||||
pub use self::generic_graph::graphviz_safe_def_name;
|
||||
pub use self::graphviz::write_mir_graphviz;
|
||||
pub use self::pretty::{
|
||||
create_dump_file, display_allocation, dump_enabled, dump_mir, write_mir_pretty, PassWhere,
|
||||
};
|
||||
|
||||
/// Types for locals
|
||||
pub type LocalDecls<'tcx> = IndexVec<Local, LocalDecl<'tcx>>;
|
||||
|
||||
|
@ -75,6 +87,22 @@ impl<'tcx> HasLocalDecls<'tcx> for Body<'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
/// A streamlined trait that you can implement to create a pass; the
|
||||
/// pass will be named after the type, and it will consist of a main
|
||||
/// loop that goes over each available MIR and applies `run_pass`.
|
||||
pub trait MirPass<'tcx> {
|
||||
fn name(&self) -> Cow<'_, str> {
|
||||
let name = std::any::type_name::<Self>();
|
||||
if let Some(tail) = name.rfind(':') {
|
||||
Cow::from(&name[tail + 1..])
|
||||
} else {
|
||||
Cow::from(name)
|
||||
}
|
||||
}
|
||||
|
||||
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>);
|
||||
}
|
||||
|
||||
/// The various "big phases" that MIR goes through.
|
||||
///
|
||||
/// These phases all describe dialects of MIR. Since all MIR uses the same datastructures, the
|
||||
|
|
173
compiler/rustc_middle/src/mir/patch.rs
Normal file
173
compiler/rustc_middle/src/mir/patch.rs
Normal file
|
@ -0,0 +1,173 @@
|
|||
use rustc_index::vec::{Idx, IndexVec};
|
||||
use rustc_middle::mir::*;
|
||||
use rustc_middle::ty::Ty;
|
||||
use rustc_span::Span;
|
||||
|
||||
/// This struct represents a patch to MIR, which can add
|
||||
/// new statements and basic blocks and patch over block
|
||||
/// terminators.
|
||||
pub struct MirPatch<'tcx> {
|
||||
patch_map: IndexVec<BasicBlock, Option<TerminatorKind<'tcx>>>,
|
||||
new_blocks: Vec<BasicBlockData<'tcx>>,
|
||||
new_statements: Vec<(Location, StatementKind<'tcx>)>,
|
||||
new_locals: Vec<LocalDecl<'tcx>>,
|
||||
resume_block: BasicBlock,
|
||||
next_local: usize,
|
||||
}
|
||||
|
||||
impl<'tcx> MirPatch<'tcx> {
|
||||
pub fn new(body: &Body<'tcx>) -> Self {
|
||||
let mut result = MirPatch {
|
||||
patch_map: IndexVec::from_elem(None, body.basic_blocks()),
|
||||
new_blocks: vec![],
|
||||
new_statements: vec![],
|
||||
new_locals: vec![],
|
||||
next_local: body.local_decls.len(),
|
||||
resume_block: START_BLOCK,
|
||||
};
|
||||
|
||||
// make sure the MIR we create has a resume block. It is
|
||||
// completely legal to convert jumps to the resume block
|
||||
// to jumps to None, but we occasionally have to add
|
||||
// instructions just before that.
|
||||
|
||||
let mut resume_block = None;
|
||||
let mut resume_stmt_block = None;
|
||||
for (bb, block) in body.basic_blocks().iter_enumerated() {
|
||||
if let TerminatorKind::Resume = block.terminator().kind {
|
||||
if !block.statements.is_empty() {
|
||||
assert!(resume_stmt_block.is_none());
|
||||
resume_stmt_block = Some(bb);
|
||||
} else {
|
||||
resume_block = Some(bb);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
let resume_block = resume_block.unwrap_or_else(|| {
|
||||
result.new_block(BasicBlockData {
|
||||
statements: vec![],
|
||||
terminator: Some(Terminator {
|
||||
source_info: SourceInfo::outermost(body.span),
|
||||
kind: TerminatorKind::Resume,
|
||||
}),
|
||||
is_cleanup: true,
|
||||
})
|
||||
});
|
||||
result.resume_block = resume_block;
|
||||
if let Some(resume_stmt_block) = resume_stmt_block {
|
||||
result
|
||||
.patch_terminator(resume_stmt_block, TerminatorKind::Goto { target: resume_block });
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn resume_block(&self) -> BasicBlock {
|
||||
self.resume_block
|
||||
}
|
||||
|
||||
pub fn is_patched(&self, bb: BasicBlock) -> bool {
|
||||
self.patch_map[bb].is_some()
|
||||
}
|
||||
|
||||
pub fn terminator_loc(&self, body: &Body<'tcx>, bb: BasicBlock) -> Location {
|
||||
let offset = match bb.index().checked_sub(body.basic_blocks().len()) {
|
||||
Some(index) => self.new_blocks[index].statements.len(),
|
||||
None => body[bb].statements.len(),
|
||||
};
|
||||
Location { block: bb, statement_index: offset }
|
||||
}
|
||||
|
||||
pub fn new_temp(&mut self, ty: Ty<'tcx>, span: Span) -> Local {
|
||||
let index = self.next_local;
|
||||
self.next_local += 1;
|
||||
self.new_locals.push(LocalDecl::new(ty, span));
|
||||
Local::new(index as usize)
|
||||
}
|
||||
|
||||
pub fn new_internal(&mut self, ty: Ty<'tcx>, span: Span) -> Local {
|
||||
let index = self.next_local;
|
||||
self.next_local += 1;
|
||||
self.new_locals.push(LocalDecl::new(ty, span).internal());
|
||||
Local::new(index as usize)
|
||||
}
|
||||
|
||||
pub fn new_block(&mut self, data: BasicBlockData<'tcx>) -> BasicBlock {
|
||||
let block = BasicBlock::new(self.patch_map.len());
|
||||
debug!("MirPatch: new_block: {:?}: {:?}", block, data);
|
||||
self.new_blocks.push(data);
|
||||
self.patch_map.push(None);
|
||||
block
|
||||
}
|
||||
|
||||
pub fn patch_terminator(&mut self, block: BasicBlock, new: TerminatorKind<'tcx>) {
|
||||
assert!(self.patch_map[block].is_none());
|
||||
debug!("MirPatch: patch_terminator({:?}, {:?})", block, new);
|
||||
self.patch_map[block] = Some(new);
|
||||
}
|
||||
|
||||
pub fn add_statement(&mut self, loc: Location, stmt: StatementKind<'tcx>) {
|
||||
debug!("MirPatch: add_statement({:?}, {:?})", loc, stmt);
|
||||
self.new_statements.push((loc, stmt));
|
||||
}
|
||||
|
||||
pub fn add_assign(&mut self, loc: Location, place: Place<'tcx>, rv: Rvalue<'tcx>) {
|
||||
self.add_statement(loc, StatementKind::Assign(Box::new((place, rv))));
|
||||
}
|
||||
|
||||
pub fn apply(self, body: &mut Body<'tcx>) {
|
||||
debug!(
|
||||
"MirPatch: {:?} new temps, starting from index {}: {:?}",
|
||||
self.new_locals.len(),
|
||||
body.local_decls.len(),
|
||||
self.new_locals
|
||||
);
|
||||
debug!(
|
||||
"MirPatch: {} new blocks, starting from index {}",
|
||||
self.new_blocks.len(),
|
||||
body.basic_blocks().len()
|
||||
);
|
||||
body.basic_blocks_mut().extend(self.new_blocks);
|
||||
body.local_decls.extend(self.new_locals);
|
||||
for (src, patch) in self.patch_map.into_iter_enumerated() {
|
||||
if let Some(patch) = patch {
|
||||
debug!("MirPatch: patching block {:?}", src);
|
||||
body[src].terminator_mut().kind = patch;
|
||||
}
|
||||
}
|
||||
|
||||
let mut new_statements = self.new_statements;
|
||||
new_statements.sort_by_key(|s| s.0);
|
||||
|
||||
let mut delta = 0;
|
||||
let mut last_bb = START_BLOCK;
|
||||
for (mut loc, stmt) in new_statements {
|
||||
if loc.block != last_bb {
|
||||
delta = 0;
|
||||
last_bb = loc.block;
|
||||
}
|
||||
debug!("MirPatch: adding statement {:?} at loc {:?}+{}", stmt, loc, delta);
|
||||
loc.statement_index += delta;
|
||||
let source_info = Self::source_info_for_index(&body[loc.block], loc);
|
||||
body[loc.block]
|
||||
.statements
|
||||
.insert(loc.statement_index, Statement { source_info, kind: stmt });
|
||||
delta += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn source_info_for_index(data: &BasicBlockData<'_>, loc: Location) -> SourceInfo {
|
||||
match data.statements.get(loc.statement_index) {
|
||||
Some(stmt) => stmt.source_info,
|
||||
None => data.terminator().source_info,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn source_info_for_location(&self, body: &Body<'_>, loc: Location) -> SourceInfo {
|
||||
let data = match loc.block.index().checked_sub(body.basic_blocks().len()) {
|
||||
Some(new) => &self.new_blocks[new],
|
||||
None => &body[loc.block],
|
||||
};
|
||||
Self::source_info_for_index(data, loc)
|
||||
}
|
||||
}
|
1046
compiler/rustc_middle/src/mir/pretty.rs
Normal file
1046
compiler/rustc_middle/src/mir/pretty.rs
Normal file
File diff suppressed because it is too large
Load diff
693
compiler/rustc_middle/src/mir/spanview.rs
Normal file
693
compiler/rustc_middle/src/mir/spanview.rs
Normal file
|
@ -0,0 +1,693 @@
|
|||
use rustc_hir::def_id::DefId;
|
||||
use rustc_middle::hir;
|
||||
use rustc_middle::mir::*;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_session::config::MirSpanview;
|
||||
use rustc_span::{BytePos, Pos, Span, SyntaxContext};
|
||||
|
||||
use std::cmp;
|
||||
use std::io::{self, Write};
|
||||
|
||||
pub const TOOLTIP_INDENT: &str = " ";
|
||||
|
||||
const CARET: char = '\u{2038}'; // Unicode `CARET`
|
||||
const ANNOTATION_LEFT_BRACKET: char = '\u{298a}'; // Unicode `Z NOTATION RIGHT BINDING BRACKET
|
||||
const ANNOTATION_RIGHT_BRACKET: char = '\u{2989}'; // Unicode `Z NOTATION LEFT BINDING BRACKET`
|
||||
const NEW_LINE_SPAN: &str = "</span>\n<span class=\"line\">";
|
||||
const HEADER: &str = r#"<!DOCTYPE html>
|
||||
<html>
|
||||
<head>"#;
|
||||
const START_BODY: &str = r#"</head>
|
||||
<body>"#;
|
||||
const FOOTER: &str = r#"</body>
|
||||
</html>"#;
|
||||
|
||||
const STYLE_SECTION: &str = r#"<style>
|
||||
.line {
|
||||
counter-increment: line;
|
||||
}
|
||||
.line:before {
|
||||
content: counter(line) ": ";
|
||||
font-family: Menlo, Monaco, monospace;
|
||||
font-style: italic;
|
||||
width: 3.8em;
|
||||
display: inline-block;
|
||||
text-align: right;
|
||||
filter: opacity(50%);
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
.code {
|
||||
color: #dddddd;
|
||||
background-color: #222222;
|
||||
font-family: Menlo, Monaco, monospace;
|
||||
line-height: 1.4em;
|
||||
border-bottom: 2px solid #222222;
|
||||
white-space: pre;
|
||||
display: inline-block;
|
||||
}
|
||||
.odd {
|
||||
background-color: #55bbff;
|
||||
color: #223311;
|
||||
}
|
||||
.even {
|
||||
background-color: #ee7756;
|
||||
color: #551133;
|
||||
}
|
||||
.code {
|
||||
--index: calc(var(--layer) - 1);
|
||||
padding-top: calc(var(--index) * 0.15em);
|
||||
filter:
|
||||
hue-rotate(calc(var(--index) * 25deg))
|
||||
saturate(calc(100% - (var(--index) * 2%)))
|
||||
brightness(calc(100% - (var(--index) * 1.5%)));
|
||||
}
|
||||
.annotation {
|
||||
color: #4444ff;
|
||||
font-family: monospace;
|
||||
font-style: italic;
|
||||
display: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
body:active .annotation {
|
||||
/* requires holding mouse down anywhere on the page */
|
||||
display: inline-block;
|
||||
}
|
||||
span:hover .annotation {
|
||||
/* requires hover over a span ONLY on its first line */
|
||||
display: inline-block;
|
||||
}
|
||||
</style>"#;
|
||||
|
||||
/// Metadata to highlight the span of a MIR BasicBlock, Statement, or Terminator.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SpanViewable {
|
||||
pub bb: BasicBlock,
|
||||
pub span: Span,
|
||||
pub id: String,
|
||||
pub tooltip: String,
|
||||
}
|
||||
|
||||
/// Write a spanview HTML+CSS file to analyze MIR element spans.
|
||||
pub fn write_mir_fn_spanview<'tcx, W>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
body: &Body<'tcx>,
|
||||
spanview: MirSpanview,
|
||||
title: &str,
|
||||
w: &mut W,
|
||||
) -> io::Result<()>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
let def_id = body.source.def_id();
|
||||
let hir_body = hir_body(tcx, def_id);
|
||||
if hir_body.is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
let body_span = hir_body.unwrap().value.span;
|
||||
let mut span_viewables = Vec::new();
|
||||
for (bb, data) in body.basic_blocks().iter_enumerated() {
|
||||
match spanview {
|
||||
MirSpanview::Statement => {
|
||||
for (i, statement) in data.statements.iter().enumerate() {
|
||||
if let Some(span_viewable) =
|
||||
statement_span_viewable(tcx, body_span, bb, i, statement)
|
||||
{
|
||||
span_viewables.push(span_viewable);
|
||||
}
|
||||
}
|
||||
if let Some(span_viewable) = terminator_span_viewable(tcx, body_span, bb, data) {
|
||||
span_viewables.push(span_viewable);
|
||||
}
|
||||
}
|
||||
MirSpanview::Terminator => {
|
||||
if let Some(span_viewable) = terminator_span_viewable(tcx, body_span, bb, data) {
|
||||
span_viewables.push(span_viewable);
|
||||
}
|
||||
}
|
||||
MirSpanview::Block => {
|
||||
if let Some(span_viewable) = block_span_viewable(tcx, body_span, bb, data) {
|
||||
span_viewables.push(span_viewable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
write_document(tcx, fn_span(tcx, def_id), span_viewables, title, w)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generate a spanview HTML+CSS document for the given local function `def_id`, and a pre-generated
|
||||
/// list `SpanViewable`s.
|
||||
pub fn write_document<'tcx, W>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
spanview_span: Span,
|
||||
mut span_viewables: Vec<SpanViewable>,
|
||||
title: &str,
|
||||
w: &mut W,
|
||||
) -> io::Result<()>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
let mut from_pos = spanview_span.lo();
|
||||
let end_pos = spanview_span.hi();
|
||||
let source_map = tcx.sess.source_map();
|
||||
let start = source_map.lookup_char_pos(from_pos);
|
||||
let indent_to_initial_start_col = " ".repeat(start.col.to_usize());
|
||||
debug!(
|
||||
"spanview_span={:?}; source is:\n{}{}",
|
||||
spanview_span,
|
||||
indent_to_initial_start_col,
|
||||
source_map.span_to_snippet(spanview_span).expect("function should have printable source")
|
||||
);
|
||||
writeln!(w, "{}", HEADER)?;
|
||||
writeln!(w, "<title>{}</title>", title)?;
|
||||
writeln!(w, "{}", STYLE_SECTION)?;
|
||||
writeln!(w, "{}", START_BODY)?;
|
||||
write!(
|
||||
w,
|
||||
r#"<div class="code" style="counter-reset: line {}"><span class="line">{}"#,
|
||||
start.line - 1,
|
||||
indent_to_initial_start_col,
|
||||
)?;
|
||||
span_viewables.sort_unstable_by(|a, b| {
|
||||
let a = a.span;
|
||||
let b = b.span;
|
||||
if a.lo() == b.lo() {
|
||||
// Sort hi() in reverse order so shorter spans are attempted after longer spans.
|
||||
// This should give shorter spans a higher "layer", so they are not covered by
|
||||
// the longer spans.
|
||||
b.hi().partial_cmp(&a.hi())
|
||||
} else {
|
||||
a.lo().partial_cmp(&b.lo())
|
||||
}
|
||||
.unwrap()
|
||||
});
|
||||
let mut ordered_viewables = &span_viewables[..];
|
||||
const LOWEST_VIEWABLE_LAYER: usize = 1;
|
||||
let mut alt = false;
|
||||
while ordered_viewables.len() > 0 {
|
||||
debug!(
|
||||
"calling write_next_viewable with from_pos={}, end_pos={}, and viewables len={}",
|
||||
from_pos.to_usize(),
|
||||
end_pos.to_usize(),
|
||||
ordered_viewables.len()
|
||||
);
|
||||
let curr_id = &ordered_viewables[0].id;
|
||||
let (next_from_pos, next_ordered_viewables) = write_next_viewable_with_overlaps(
|
||||
tcx,
|
||||
from_pos,
|
||||
end_pos,
|
||||
ordered_viewables,
|
||||
alt,
|
||||
LOWEST_VIEWABLE_LAYER,
|
||||
w,
|
||||
)?;
|
||||
debug!(
|
||||
"DONE calling write_next_viewable, with new from_pos={}, \
|
||||
and remaining viewables len={}",
|
||||
next_from_pos.to_usize(),
|
||||
next_ordered_viewables.len()
|
||||
);
|
||||
assert!(
|
||||
from_pos != next_from_pos || ordered_viewables.len() != next_ordered_viewables.len(),
|
||||
"write_next_viewable_with_overlaps() must make a state change"
|
||||
);
|
||||
from_pos = next_from_pos;
|
||||
if next_ordered_viewables.len() != ordered_viewables.len() {
|
||||
ordered_viewables = next_ordered_viewables;
|
||||
if let Some(next_ordered_viewable) = ordered_viewables.first() {
|
||||
if &next_ordered_viewable.id != curr_id {
|
||||
alt = !alt;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if from_pos < end_pos {
|
||||
write_coverage_gap(tcx, from_pos, end_pos, w)?;
|
||||
}
|
||||
writeln!(w, r#"</span></div>"#)?;
|
||||
writeln!(w, "{}", FOOTER)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Format a string showing the start line and column, and end line and column within a file.
|
||||
pub fn source_range_no_file<'tcx>(tcx: TyCtxt<'tcx>, span: &Span) -> String {
|
||||
let source_map = tcx.sess.source_map();
|
||||
let start = source_map.lookup_char_pos(span.lo());
|
||||
let end = source_map.lookup_char_pos(span.hi());
|
||||
format!("{}:{}-{}:{}", start.line, start.col.to_usize() + 1, end.line, end.col.to_usize() + 1)
|
||||
}
|
||||
|
||||
pub fn statement_kind_name(statement: &Statement<'_>) -> &'static str {
|
||||
use StatementKind::*;
|
||||
match statement.kind {
|
||||
Assign(..) => "Assign",
|
||||
FakeRead(..) => "FakeRead",
|
||||
SetDiscriminant { .. } => "SetDiscriminant",
|
||||
StorageLive(..) => "StorageLive",
|
||||
StorageDead(..) => "StorageDead",
|
||||
LlvmInlineAsm(..) => "LlvmInlineAsm",
|
||||
Retag(..) => "Retag",
|
||||
AscribeUserType(..) => "AscribeUserType",
|
||||
Coverage(..) => "Coverage",
|
||||
CopyNonOverlapping(..) => "CopyNonOverlapping",
|
||||
Nop => "Nop",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn terminator_kind_name(term: &Terminator<'_>) -> &'static str {
|
||||
use TerminatorKind::*;
|
||||
match term.kind {
|
||||
Goto { .. } => "Goto",
|
||||
SwitchInt { .. } => "SwitchInt",
|
||||
Resume => "Resume",
|
||||
Abort => "Abort",
|
||||
Return => "Return",
|
||||
Unreachable => "Unreachable",
|
||||
Drop { .. } => "Drop",
|
||||
DropAndReplace { .. } => "DropAndReplace",
|
||||
Call { .. } => "Call",
|
||||
Assert { .. } => "Assert",
|
||||
Yield { .. } => "Yield",
|
||||
GeneratorDrop => "GeneratorDrop",
|
||||
FalseEdge { .. } => "FalseEdge",
|
||||
FalseUnwind { .. } => "FalseUnwind",
|
||||
InlineAsm { .. } => "InlineAsm",
|
||||
}
|
||||
}
|
||||
|
||||
fn statement_span_viewable<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
body_span: Span,
|
||||
bb: BasicBlock,
|
||||
i: usize,
|
||||
statement: &Statement<'tcx>,
|
||||
) -> Option<SpanViewable> {
|
||||
let span = statement.source_info.span;
|
||||
if !body_span.contains(span) {
|
||||
return None;
|
||||
}
|
||||
let id = format!("{}[{}]", bb.index(), i);
|
||||
let tooltip = tooltip(tcx, &id, span, vec![statement.clone()], &None);
|
||||
Some(SpanViewable { bb, span, id, tooltip })
|
||||
}
|
||||
|
||||
fn terminator_span_viewable<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
body_span: Span,
|
||||
bb: BasicBlock,
|
||||
data: &BasicBlockData<'tcx>,
|
||||
) -> Option<SpanViewable> {
|
||||
let term = data.terminator();
|
||||
let span = term.source_info.span;
|
||||
if !body_span.contains(span) {
|
||||
return None;
|
||||
}
|
||||
let id = format!("{}:{}", bb.index(), terminator_kind_name(term));
|
||||
let tooltip = tooltip(tcx, &id, span, vec![], &data.terminator);
|
||||
Some(SpanViewable { bb, span, id, tooltip })
|
||||
}
|
||||
|
||||
fn block_span_viewable<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
body_span: Span,
|
||||
bb: BasicBlock,
|
||||
data: &BasicBlockData<'tcx>,
|
||||
) -> Option<SpanViewable> {
|
||||
let span = compute_block_span(data, body_span);
|
||||
if !body_span.contains(span) {
|
||||
return None;
|
||||
}
|
||||
let id = format!("{}", bb.index());
|
||||
let tooltip = tooltip(tcx, &id, span, data.statements.clone(), &data.terminator);
|
||||
Some(SpanViewable { bb, span, id, tooltip })
|
||||
}
|
||||
|
||||
fn compute_block_span<'tcx>(data: &BasicBlockData<'tcx>, body_span: Span) -> Span {
|
||||
let mut span = data.terminator().source_info.span;
|
||||
for statement_span in data.statements.iter().map(|statement| statement.source_info.span) {
|
||||
// Only combine Spans from the root context, and within the function's body_span.
|
||||
if statement_span.ctxt() == SyntaxContext::root() && body_span.contains(statement_span) {
|
||||
span = span.to(statement_span);
|
||||
}
|
||||
}
|
||||
span
|
||||
}
|
||||
|
||||
/// Recursively process each ordered span. Spans that overlap will have progressively varying
|
||||
/// styles, such as increased padding for each overlap. Non-overlapping adjacent spans will
|
||||
/// have alternating style choices, to help distinguish between them if, visually adjacent.
|
||||
/// The `layer` is incremented for each overlap, and the `alt` bool alternates between true
|
||||
/// and false, for each adjacent non-overlapping span. Source code between the spans (code
|
||||
/// that is not in any coverage region) has neutral styling.
|
||||
fn write_next_viewable_with_overlaps<'tcx, 'b, W>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
mut from_pos: BytePos,
|
||||
mut to_pos: BytePos,
|
||||
ordered_viewables: &'b [SpanViewable],
|
||||
alt: bool,
|
||||
layer: usize,
|
||||
w: &mut W,
|
||||
) -> io::Result<(BytePos, &'b [SpanViewable])>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
let debug_indent = " ".repeat(layer);
|
||||
let (viewable, mut remaining_viewables) =
|
||||
ordered_viewables.split_first().expect("ordered_viewables should have some");
|
||||
|
||||
if from_pos < viewable.span.lo() {
|
||||
debug!(
|
||||
"{}advance from_pos to next SpanViewable (from from_pos={} to viewable.span.lo()={} \
|
||||
of {:?}), with to_pos={}",
|
||||
debug_indent,
|
||||
from_pos.to_usize(),
|
||||
viewable.span.lo().to_usize(),
|
||||
viewable.span,
|
||||
to_pos.to_usize()
|
||||
);
|
||||
let hi = cmp::min(viewable.span.lo(), to_pos);
|
||||
write_coverage_gap(tcx, from_pos, hi, w)?;
|
||||
from_pos = hi;
|
||||
if from_pos < viewable.span.lo() {
|
||||
debug!(
|
||||
"{}EARLY RETURN: stopped before getting to next SpanViewable, at {}",
|
||||
debug_indent,
|
||||
from_pos.to_usize()
|
||||
);
|
||||
return Ok((from_pos, ordered_viewables));
|
||||
}
|
||||
}
|
||||
|
||||
if from_pos < viewable.span.hi() {
|
||||
// Set to_pos to the end of this `viewable` to ensure the recursive calls stop writing
|
||||
// with room to print the tail.
|
||||
to_pos = cmp::min(viewable.span.hi(), to_pos);
|
||||
debug!(
|
||||
"{}update to_pos (if not closer) to viewable.span.hi()={}; to_pos is now {}",
|
||||
debug_indent,
|
||||
viewable.span.hi().to_usize(),
|
||||
to_pos.to_usize()
|
||||
);
|
||||
}
|
||||
|
||||
let mut subalt = false;
|
||||
while remaining_viewables.len() > 0 && remaining_viewables[0].span.overlaps(viewable.span) {
|
||||
let overlapping_viewable = &remaining_viewables[0];
|
||||
debug!("{}overlapping_viewable.span={:?}", debug_indent, overlapping_viewable.span);
|
||||
|
||||
let span =
|
||||
trim_span(viewable.span, from_pos, cmp::min(overlapping_viewable.span.lo(), to_pos));
|
||||
let mut some_html_snippet = if from_pos <= viewable.span.hi() || viewable.span.is_empty() {
|
||||
// `viewable` is not yet fully rendered, so start writing the span, up to either the
|
||||
// `to_pos` or the next `overlapping_viewable`, whichever comes first.
|
||||
debug!(
|
||||
"{}make html_snippet (may not write it if early exit) for partial span {:?} \
|
||||
of viewable.span {:?}",
|
||||
debug_indent, span, viewable.span
|
||||
);
|
||||
from_pos = span.hi();
|
||||
make_html_snippet(tcx, span, Some(&viewable))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Defer writing the HTML snippet (until after early return checks) ONLY for empty spans.
|
||||
// An empty Span with Some(html_snippet) is probably a tail marker. If there is an early
|
||||
// exit, there should be another opportunity to write the tail marker.
|
||||
if !span.is_empty() {
|
||||
if let Some(ref html_snippet) = some_html_snippet {
|
||||
debug!(
|
||||
"{}write html_snippet for that partial span of viewable.span {:?}",
|
||||
debug_indent, viewable.span
|
||||
);
|
||||
write_span(html_snippet, &viewable.tooltip, alt, layer, w)?;
|
||||
}
|
||||
some_html_snippet = None;
|
||||
}
|
||||
|
||||
if from_pos < overlapping_viewable.span.lo() {
|
||||
debug!(
|
||||
"{}EARLY RETURN: from_pos={} has not yet reached the \
|
||||
overlapping_viewable.span {:?}",
|
||||
debug_indent,
|
||||
from_pos.to_usize(),
|
||||
overlapping_viewable.span
|
||||
);
|
||||
// must have reached `to_pos` before reaching the start of the
|
||||
// `overlapping_viewable.span`
|
||||
return Ok((from_pos, ordered_viewables));
|
||||
}
|
||||
|
||||
if from_pos == to_pos
|
||||
&& !(from_pos == overlapping_viewable.span.lo() && overlapping_viewable.span.is_empty())
|
||||
{
|
||||
debug!(
|
||||
"{}EARLY RETURN: from_pos=to_pos={} and overlapping_viewable.span {:?} is not \
|
||||
empty, or not from_pos",
|
||||
debug_indent,
|
||||
to_pos.to_usize(),
|
||||
overlapping_viewable.span
|
||||
);
|
||||
// `to_pos` must have occurred before the overlapping viewable. Return
|
||||
// `ordered_viewables` so we can continue rendering the `viewable`, from after the
|
||||
// `to_pos`.
|
||||
return Ok((from_pos, ordered_viewables));
|
||||
}
|
||||
|
||||
if let Some(ref html_snippet) = some_html_snippet {
|
||||
debug!(
|
||||
"{}write html_snippet for that partial span of viewable.span {:?}",
|
||||
debug_indent, viewable.span
|
||||
);
|
||||
write_span(html_snippet, &viewable.tooltip, alt, layer, w)?;
|
||||
}
|
||||
|
||||
debug!(
|
||||
"{}recursively calling write_next_viewable with from_pos={}, to_pos={}, \
|
||||
and viewables len={}",
|
||||
debug_indent,
|
||||
from_pos.to_usize(),
|
||||
to_pos.to_usize(),
|
||||
remaining_viewables.len()
|
||||
);
|
||||
// Write the overlaps (and the overlaps' overlaps, if any) up to `to_pos`.
|
||||
let curr_id = &remaining_viewables[0].id;
|
||||
let (next_from_pos, next_remaining_viewables) = write_next_viewable_with_overlaps(
|
||||
tcx,
|
||||
from_pos,
|
||||
to_pos,
|
||||
&remaining_viewables,
|
||||
subalt,
|
||||
layer + 1,
|
||||
w,
|
||||
)?;
|
||||
debug!(
|
||||
"{}DONE recursively calling write_next_viewable, with new from_pos={}, and remaining \
|
||||
viewables len={}",
|
||||
debug_indent,
|
||||
next_from_pos.to_usize(),
|
||||
next_remaining_viewables.len()
|
||||
);
|
||||
assert!(
|
||||
from_pos != next_from_pos
|
||||
|| remaining_viewables.len() != next_remaining_viewables.len(),
|
||||
"write_next_viewable_with_overlaps() must make a state change"
|
||||
);
|
||||
from_pos = next_from_pos;
|
||||
if next_remaining_viewables.len() != remaining_viewables.len() {
|
||||
remaining_viewables = next_remaining_viewables;
|
||||
if let Some(next_ordered_viewable) = remaining_viewables.first() {
|
||||
if &next_ordered_viewable.id != curr_id {
|
||||
subalt = !subalt;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if from_pos <= viewable.span.hi() {
|
||||
let span = trim_span(viewable.span, from_pos, to_pos);
|
||||
debug!(
|
||||
"{}After overlaps, writing (end span?) {:?} of viewable.span {:?}",
|
||||
debug_indent, span, viewable.span
|
||||
);
|
||||
if let Some(ref html_snippet) = make_html_snippet(tcx, span, Some(&viewable)) {
|
||||
from_pos = span.hi();
|
||||
write_span(html_snippet, &viewable.tooltip, alt, layer, w)?;
|
||||
}
|
||||
}
|
||||
debug!("{}RETURN: No more overlap", debug_indent);
|
||||
Ok((
|
||||
from_pos,
|
||||
if from_pos < viewable.span.hi() { ordered_viewables } else { remaining_viewables },
|
||||
))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn write_coverage_gap<'tcx, W>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
lo: BytePos,
|
||||
hi: BytePos,
|
||||
w: &mut W,
|
||||
) -> io::Result<()>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
let span = Span::with_root_ctxt(lo, hi);
|
||||
if let Some(ref html_snippet) = make_html_snippet(tcx, span, None) {
|
||||
write_span(html_snippet, "", false, 0, w)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn write_span<W>(
|
||||
html_snippet: &str,
|
||||
tooltip: &str,
|
||||
alt: bool,
|
||||
layer: usize,
|
||||
w: &mut W,
|
||||
) -> io::Result<()>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
let maybe_alt_class = if layer > 0 {
|
||||
if alt { " odd" } else { " even" }
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let maybe_title_attr = if !tooltip.is_empty() {
|
||||
format!(" title=\"{}\"", escape_attr(tooltip))
|
||||
} else {
|
||||
"".to_owned()
|
||||
};
|
||||
if layer == 1 {
|
||||
write!(w, "<span>")?;
|
||||
}
|
||||
for (i, line) in html_snippet.lines().enumerate() {
|
||||
if i > 0 {
|
||||
write!(w, "{}", NEW_LINE_SPAN)?;
|
||||
}
|
||||
write!(
|
||||
w,
|
||||
r#"<span class="code{}" style="--layer: {}"{}>{}</span>"#,
|
||||
maybe_alt_class, layer, maybe_title_attr, line
|
||||
)?;
|
||||
}
|
||||
// Check for and translate trailing newlines, because `str::lines()` ignores them
|
||||
if html_snippet.ends_with('\n') {
|
||||
write!(w, "{}", NEW_LINE_SPAN)?;
|
||||
}
|
||||
if layer == 1 {
|
||||
write!(w, "</span>")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn make_html_snippet<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
span: Span,
|
||||
some_viewable: Option<&SpanViewable>,
|
||||
) -> Option<String> {
|
||||
let source_map = tcx.sess.source_map();
|
||||
let snippet = source_map
|
||||
.span_to_snippet(span)
|
||||
.unwrap_or_else(|err| bug!("span_to_snippet error for span {:?}: {:?}", span, err));
|
||||
let html_snippet = if let Some(viewable) = some_viewable {
|
||||
let is_head = span.lo() == viewable.span.lo();
|
||||
let is_tail = span.hi() == viewable.span.hi();
|
||||
let mut labeled_snippet = if is_head {
|
||||
format!(r#"<span class="annotation">{}{}</span>"#, viewable.id, ANNOTATION_LEFT_BRACKET)
|
||||
} else {
|
||||
"".to_owned()
|
||||
};
|
||||
if span.is_empty() {
|
||||
if is_head && is_tail {
|
||||
labeled_snippet.push(CARET);
|
||||
}
|
||||
} else {
|
||||
labeled_snippet.push_str(&escape_html(&snippet));
|
||||
};
|
||||
if is_tail {
|
||||
labeled_snippet.push_str(&format!(
|
||||
r#"<span class="annotation">{}{}</span>"#,
|
||||
ANNOTATION_RIGHT_BRACKET, viewable.id
|
||||
));
|
||||
}
|
||||
labeled_snippet
|
||||
} else {
|
||||
escape_html(&snippet)
|
||||
};
|
||||
if html_snippet.is_empty() { None } else { Some(html_snippet) }
|
||||
}
|
||||
|
||||
fn tooltip<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
spanview_id: &str,
|
||||
span: Span,
|
||||
statements: Vec<Statement<'tcx>>,
|
||||
terminator: &Option<Terminator<'tcx>>,
|
||||
) -> String {
|
||||
let source_map = tcx.sess.source_map();
|
||||
let mut text = Vec::new();
|
||||
text.push(format!("{}: {}:", spanview_id, &source_map.span_to_embeddable_string(span)));
|
||||
for statement in statements {
|
||||
let source_range = source_range_no_file(tcx, &statement.source_info.span);
|
||||
text.push(format!(
|
||||
"\n{}{}: {}: {}",
|
||||
TOOLTIP_INDENT,
|
||||
source_range,
|
||||
statement_kind_name(&statement),
|
||||
format!("{:?}", statement)
|
||||
));
|
||||
}
|
||||
if let Some(term) = terminator {
|
||||
let source_range = source_range_no_file(tcx, &term.source_info.span);
|
||||
text.push(format!(
|
||||
"\n{}{}: {}: {:?}",
|
||||
TOOLTIP_INDENT,
|
||||
source_range,
|
||||
terminator_kind_name(term),
|
||||
term.kind
|
||||
));
|
||||
}
|
||||
text.join("")
|
||||
}
|
||||
|
||||
fn trim_span(span: Span, from_pos: BytePos, to_pos: BytePos) -> Span {
|
||||
trim_span_hi(trim_span_lo(span, from_pos), to_pos)
|
||||
}
|
||||
|
||||
fn trim_span_lo(span: Span, from_pos: BytePos) -> Span {
|
||||
if from_pos <= span.lo() { span } else { span.with_lo(cmp::min(span.hi(), from_pos)) }
|
||||
}
|
||||
|
||||
fn trim_span_hi(span: Span, to_pos: BytePos) -> Span {
|
||||
if to_pos >= span.hi() { span } else { span.with_hi(cmp::max(span.lo(), to_pos)) }
|
||||
}
|
||||
|
||||
fn fn_span<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> Span {
|
||||
let hir_id =
|
||||
tcx.hir().local_def_id_to_hir_id(def_id.as_local().expect("expected DefId is local"));
|
||||
let fn_decl_span = tcx.hir().span(hir_id);
|
||||
if let Some(body_span) = hir_body(tcx, def_id).map(|hir_body| hir_body.value.span) {
|
||||
if fn_decl_span.ctxt() == body_span.ctxt() { fn_decl_span.to(body_span) } else { body_span }
|
||||
} else {
|
||||
fn_decl_span
|
||||
}
|
||||
}
|
||||
|
||||
fn hir_body<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> Option<&'tcx rustc_hir::Body<'tcx>> {
|
||||
let hir_node = tcx.hir().get_if_local(def_id).expect("expected DefId is local");
|
||||
hir::map::associated_body(hir_node).map(|fn_body_id| tcx.hir().body(fn_body_id))
|
||||
}
|
||||
|
||||
fn escape_html(s: &str) -> String {
|
||||
s.replace("&", "&").replace("<", "<").replace(">", ">")
|
||||
}
|
||||
|
||||
fn escape_attr(s: &str) -> String {
|
||||
s.replace("&", "&")
|
||||
.replace("\"", """)
|
||||
.replace("'", "'")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue