add code to persist graph and for unit-testing
This commit is contained in:
parent
d8263c4758
commit
3fb40c1d95
|
@ -829,6 +829,10 @@ pub fn phase_3_run_analysis_passes<'tcx, F, R>(sess: &'tcx Session,
|
||||||
index,
|
index,
|
||||||
name,
|
name,
|
||||||
|tcx| {
|
|tcx| {
|
||||||
|
time(time_passes,
|
||||||
|
"load_dep_graph",
|
||||||
|
|| rustc_incremental::load_dep_graph(tcx));
|
||||||
|
|
||||||
// passes are timed inside typeck
|
// passes are timed inside typeck
|
||||||
try_with_f!(typeck::check_crate(tcx, trait_map), (tcx, None, analysis));
|
try_with_f!(typeck::check_crate(tcx, trait_map), (tcx, None, analysis));
|
||||||
|
|
||||||
|
@ -962,6 +966,10 @@ pub fn phase_4_translate_to_llvm<'tcx>(tcx: &TyCtxt<'tcx>,
|
||||||
"assert dep graph",
|
"assert dep graph",
|
||||||
move || rustc_incremental::assert_dep_graph(tcx));
|
move || rustc_incremental::assert_dep_graph(tcx));
|
||||||
|
|
||||||
|
time(time_passes,
|
||||||
|
"serialize dep graph",
|
||||||
|
move || rustc_incremental::save_dep_graph(tcx));
|
||||||
|
|
||||||
translation
|
translation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
|
||||||
|
// file at the top-level directory of this distribution and at
|
||||||
|
// http://rust-lang.org/COPYRIGHT.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||||
|
// option. This file may not be copied, modified, or distributed
|
||||||
|
// except according to those terms.
|
||||||
|
|
||||||
|
//! The data that we will serialize and deserialize.
|
||||||
|
|
||||||
|
use rustc::dep_graph::DepNode;
|
||||||
|
use rustc_serialize::{Decoder as RustcDecoder,
|
||||||
|
Encodable as RustcEncodable, Encoder as RustcEncoder};
|
||||||
|
|
||||||
|
use super::directory::DefPathIndex;
|
||||||
|
|
||||||
|
#[derive(Debug, RustcEncodable, RustcDecodable)]
|
||||||
|
pub struct SerializedDepGraph {
|
||||||
|
pub nodes: Vec<DepNode<DefPathIndex>>,
|
||||||
|
pub edges: Vec<SerializedEdge>,
|
||||||
|
pub hashes: Vec<SerializedHash>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type SerializedEdge = (DepNode<DefPathIndex>, DepNode<DefPathIndex>);
|
||||||
|
|
||||||
|
#[derive(Debug, RustcEncodable, RustcDecodable)]
|
||||||
|
pub struct SerializedHash {
|
||||||
|
pub index: DefPathIndex,
|
||||||
|
|
||||||
|
/// the hash itself, computed by `calculate_item_hash`
|
||||||
|
pub hash: u64,
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,118 @@
|
||||||
|
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
|
||||||
|
// file at the top-level directory of this distribution and at
|
||||||
|
// http://rust-lang.org/COPYRIGHT.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||||
|
// option. This file may not be copied, modified, or distributed
|
||||||
|
// except according to those terms.
|
||||||
|
|
||||||
|
//! Code to convert a DefId into a DefPath (when serializing) and then
|
||||||
|
//! back again (when deserializing). Note that the new DefId
|
||||||
|
//! necessarily will not be the same as the old (and of course the
|
||||||
|
//! item might even be removed in the meantime).
|
||||||
|
|
||||||
|
use rustc::dep_graph::DepNode;
|
||||||
|
use rustc::front::map::DefPath;
|
||||||
|
use rustc::middle::def_id::DefId;
|
||||||
|
use rustc::ty;
|
||||||
|
use rustc::util::nodemap::DefIdMap;
|
||||||
|
use rustc_serialize::{Decoder as RustcDecoder,
|
||||||
|
Encodable as RustcEncodable, Encoder as RustcEncoder};
|
||||||
|
use std::fmt::{self, Debug};
|
||||||
|
|
||||||
|
/// Index into the DefIdDirectory
|
||||||
|
#[derive(Copy, Clone, Debug, PartialOrd, Ord, Hash, PartialEq, Eq,
|
||||||
|
RustcEncodable, RustcDecodable)]
|
||||||
|
pub struct DefPathIndex {
|
||||||
|
index: u32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(RustcEncodable, RustcDecodable)]
|
||||||
|
pub struct DefIdDirectory {
|
||||||
|
// N.B. don't use Removable here because these def-ids are loaded
|
||||||
|
// directly without remapping, so loading them should not fail.
|
||||||
|
paths: Vec<DefPath>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DefIdDirectory {
|
||||||
|
pub fn new() -> DefIdDirectory {
|
||||||
|
DefIdDirectory { paths: vec![] }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn retrace(&self, tcx: &ty::TyCtxt) -> RetracedDefIdDirectory {
|
||||||
|
let ids = self.paths.iter()
|
||||||
|
.map(|path| tcx.map.retrace_path(path))
|
||||||
|
.collect();
|
||||||
|
RetracedDefIdDirectory { ids: ids }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, RustcEncodable, RustcDecodable)]
|
||||||
|
pub struct RetracedDefIdDirectory {
|
||||||
|
ids: Vec<Option<DefId>>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RetracedDefIdDirectory {
|
||||||
|
pub fn def_id(&self, index: DefPathIndex) -> Option<DefId> {
|
||||||
|
self.ids[index.index as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map(&self, node: DepNode<DefPathIndex>) -> Option<DepNode<DefId>> {
|
||||||
|
node.map_def(|&index| self.def_id(index))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DefIdDirectoryBuilder<'a,'tcx:'a> {
|
||||||
|
tcx: &'a ty::TyCtxt<'tcx>,
|
||||||
|
hash: DefIdMap<Option<DefPathIndex>>,
|
||||||
|
directory: DefIdDirectory,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a,'tcx> DefIdDirectoryBuilder<'a,'tcx> {
|
||||||
|
pub fn new(tcx: &'a ty::TyCtxt<'tcx>) -> DefIdDirectoryBuilder<'a, 'tcx> {
|
||||||
|
DefIdDirectoryBuilder {
|
||||||
|
tcx: tcx,
|
||||||
|
hash: DefIdMap(),
|
||||||
|
directory: DefIdDirectory::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(&mut self, def_id: DefId) -> Option<DefPathIndex> {
|
||||||
|
if !def_id.is_local() {
|
||||||
|
// FIXME(#32015) clarify story about cross-crate dep tracking
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tcx = self.tcx;
|
||||||
|
let paths = &mut self.directory.paths;
|
||||||
|
self.hash.entry(def_id)
|
||||||
|
.or_insert_with(|| {
|
||||||
|
let def_path = tcx.def_path(def_id);
|
||||||
|
if !def_path.is_local() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let index = paths.len() as u32;
|
||||||
|
paths.push(def_path);
|
||||||
|
Some(DefPathIndex { index: index })
|
||||||
|
})
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map(&mut self, node: DepNode<DefId>) -> Option<DepNode<DefPathIndex>> {
|
||||||
|
node.map_def(|&def_id| self.add(def_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_directory(self) -> DefIdDirectory {
|
||||||
|
self.directory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for DefIdDirectory {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
fmt.debug_list()
|
||||||
|
.entries(self.paths.iter().enumerate())
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,151 @@
|
||||||
|
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
|
||||||
|
// file at the top-level directory of this distribution and at
|
||||||
|
// http://rust-lang.org/COPYRIGHT.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||||
|
// option. This file may not be copied, modified, or distributed
|
||||||
|
// except according to those terms.
|
||||||
|
|
||||||
|
//! Debugging code to test the state of the dependency graph just
|
||||||
|
//! after it is loaded from disk. For each node marked with
|
||||||
|
//! `#[rustc_clean]` or `#[rustc_dirty]`, we will check that a
|
||||||
|
//! suitable node for that item either appears or does not appear in
|
||||||
|
//! the dep-graph, as appropriate:
|
||||||
|
//!
|
||||||
|
//! - `#[rustc_dirty(label="TypeckItemBody", cfg="rev2")]` if we are
|
||||||
|
//! in `#[cfg(rev2)]`, then there MUST NOT be a node
|
||||||
|
//! `DepNode::TypeckItemBody(X)` where `X` is the def-id of the
|
||||||
|
//! current node.
|
||||||
|
//! - `#[rustc_clean(label="TypeckItemBody", cfg="rev2")]` same as above,
|
||||||
|
//! except that the node MUST exist.
|
||||||
|
//!
|
||||||
|
//! Errors are reported if we are in the suitable configuration but
|
||||||
|
//! the required condition is not met.
|
||||||
|
|
||||||
|
use rustc::dep_graph::{DepGraphQuery, DepNode};
|
||||||
|
use rustc::middle::def_id::DefId;
|
||||||
|
use rustc_front::hir;
|
||||||
|
use rustc_front::intravisit::Visitor;
|
||||||
|
use syntax::ast::{self, Attribute, MetaItem};
|
||||||
|
use syntax::attr::AttrMetaMethods;
|
||||||
|
use syntax::parse::token::InternedString;
|
||||||
|
use rustc::ty;
|
||||||
|
|
||||||
|
const DIRTY: &'static str = "rustc_dirty";
|
||||||
|
const CLEAN: &'static str = "rustc_clean";
|
||||||
|
const LABEL: &'static str = "label";
|
||||||
|
const CFG: &'static str = "cfg";
|
||||||
|
|
||||||
|
pub fn check_dirty_clean_annotations(tcx: &ty::TyCtxt) {
|
||||||
|
let _ignore = tcx.dep_graph.in_ignore();
|
||||||
|
let query = tcx.dep_graph.query();
|
||||||
|
let krate = tcx.map.krate();
|
||||||
|
krate.visit_all_items(&mut DirtyCleanVisitor {
|
||||||
|
tcx: tcx,
|
||||||
|
query: &query,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DirtyCleanVisitor<'a, 'tcx:'a> {
|
||||||
|
tcx: &'a ty::TyCtxt<'tcx>,
|
||||||
|
query: &'a DepGraphQuery<DefId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'tcx> DirtyCleanVisitor<'a, 'tcx> {
|
||||||
|
fn expect_associated_value(&self, item: &MetaItem) -> InternedString {
|
||||||
|
if let Some(value) = item.value_str() {
|
||||||
|
value
|
||||||
|
} else {
|
||||||
|
self.tcx.sess.span_fatal(
|
||||||
|
item.span,
|
||||||
|
&format!("associated value expected for `{}`", item.name()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Given a `#[rustc_dirty]` or `#[rustc_clean]` attribute, scan
|
||||||
|
/// for a `cfg="foo"` attribute and check whether we have a cfg
|
||||||
|
/// flag called `foo`.
|
||||||
|
fn check_config(&self, attr: &ast::Attribute) -> bool {
|
||||||
|
debug!("check_config(attr={:?})", attr);
|
||||||
|
let config = &self.tcx.map.krate().config;
|
||||||
|
debug!("check_config: config={:?}", config);
|
||||||
|
for item in attr.meta_item_list().unwrap_or(&[]) {
|
||||||
|
if item.check_name(CFG) {
|
||||||
|
let value = self.expect_associated_value(item);
|
||||||
|
debug!("check_config: searching for cfg {:?}", value);
|
||||||
|
for cfg in &config[..] {
|
||||||
|
if cfg.check_name(&value[..]) {
|
||||||
|
debug!("check_config: matched {:?}", cfg);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debug!("check_config: no match found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dep_node(&self, attr: &Attribute, def_id: DefId) -> DepNode<DefId> {
|
||||||
|
for item in attr.meta_item_list().unwrap_or(&[]) {
|
||||||
|
if item.check_name(LABEL) {
|
||||||
|
let value = self.expect_associated_value(item);
|
||||||
|
match DepNode::from_label_string(&value[..], def_id) {
|
||||||
|
Ok(def_id) => return def_id,
|
||||||
|
Err(()) => {
|
||||||
|
self.tcx.sess.span_fatal(
|
||||||
|
item.span,
|
||||||
|
&format!("dep-node label `{}` not recognized", value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.tcx.sess.span_fatal(attr.span, "no `label` found");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dep_node_str(&self, dep_node: DepNode<DefId>) -> DepNode<String> {
|
||||||
|
dep_node.map_def(|&def_id| Some(self.tcx.item_path_str(def_id))).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_dirty(&self, item: &hir::Item, dep_node: DepNode<DefId>) {
|
||||||
|
debug!("assert_dirty({:?})", dep_node);
|
||||||
|
|
||||||
|
if self.query.contains_node(&dep_node) {
|
||||||
|
let dep_node_str = self.dep_node_str(dep_node);
|
||||||
|
self.tcx.sess.span_err(
|
||||||
|
item.span,
|
||||||
|
&format!("`{:?}` found in dep graph, but should be dirty", dep_node_str));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_clean(&self, item: &hir::Item, dep_node: DepNode<DefId>) {
|
||||||
|
debug!("assert_clean({:?})", dep_node);
|
||||||
|
|
||||||
|
if !self.query.contains_node(&dep_node) {
|
||||||
|
let dep_node_str = self.dep_node_str(dep_node);
|
||||||
|
self.tcx.sess.span_err(
|
||||||
|
item.span,
|
||||||
|
&format!("`{:?}` not found in dep graph, but should be clean", dep_node_str));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'tcx> Visitor<'tcx> for DirtyCleanVisitor<'a, 'tcx> {
|
||||||
|
fn visit_item(&mut self, item: &'tcx hir::Item) {
|
||||||
|
let def_id = self.tcx.map.local_def_id(item.id);
|
||||||
|
for attr in self.tcx.get_attrs(def_id).iter() {
|
||||||
|
if attr.check_name(DIRTY) {
|
||||||
|
if self.check_config(attr) {
|
||||||
|
self.assert_dirty(item, self.dep_node(attr, def_id));
|
||||||
|
}
|
||||||
|
} else if attr.check_name(CLEAN) {
|
||||||
|
if self.check_config(attr) {
|
||||||
|
self.assert_clean(item, self.dep_node(attr, def_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,221 @@
|
||||||
|
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
|
||||||
|
// file at the top-level directory of this distribution and at
|
||||||
|
// http://rust-lang.org/COPYRIGHT.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||||
|
// option. This file may not be copied, modified, or distributed
|
||||||
|
// except according to those terms.
|
||||||
|
|
||||||
|
//! Code to save/load the dep-graph from files.
|
||||||
|
|
||||||
|
|
||||||
|
use calculate_svh::SvhCalculate;
|
||||||
|
use rbml::{self, Doc};
|
||||||
|
use rbml::reader::{self, DecodeResult, Decoder};
|
||||||
|
use rustc::dep_graph::DepNode;
|
||||||
|
use rustc::middle::def_id::DefId;
|
||||||
|
use rustc::ty;
|
||||||
|
use rustc_data_structures::fnv::FnvHashSet;
|
||||||
|
use rustc_serialize::Decodable as RustcDecodable;
|
||||||
|
use std::io::Read;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use super::data::*;
|
||||||
|
use super::directory::*;
|
||||||
|
use super::dirty_clean;
|
||||||
|
use super::util::*;
|
||||||
|
|
||||||
|
type DirtyNodes = FnvHashSet<DepNode<DefId>>;
|
||||||
|
|
||||||
|
type CleanEdges = Vec<(DepNode<DefId>, DepNode<DefId>)>;
|
||||||
|
|
||||||
|
pub fn load_dep_graph<'tcx>(tcx: &ty::TyCtxt<'tcx>) {
|
||||||
|
let _ignore = tcx.dep_graph.in_ignore();
|
||||||
|
|
||||||
|
if let Some(dep_graph) = dep_graph_path(tcx) {
|
||||||
|
load_dep_graph_if_exists(tcx, &dep_graph);
|
||||||
|
dirty_clean::check_dirty_clean_annotations(tcx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_dep_graph_if_exists<'tcx>(tcx: &ty::TyCtxt<'tcx>, path: &Path) {
|
||||||
|
if !path.exists() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut data = vec![];
|
||||||
|
match
|
||||||
|
File::open(path)
|
||||||
|
.and_then(|mut file| file.read_to_end(&mut data))
|
||||||
|
{
|
||||||
|
Ok(_) => { }
|
||||||
|
Err(err) => {
|
||||||
|
tcx.sess.err(
|
||||||
|
&format!("could not load dep-graph from `{}`: {}",
|
||||||
|
path.display(), err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match decode_dep_graph(tcx, Doc::new(&data)) {
|
||||||
|
Ok(dirty) => dirty,
|
||||||
|
Err(err) => {
|
||||||
|
tcx.sess.bug(
|
||||||
|
&format!("decoding error in dep-graph from `{}`: {}",
|
||||||
|
path.display(), err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode_dep_graph<'tcx, 'doc>(tcx: &ty::TyCtxt<'tcx>, doc: rbml::Doc<'doc>)
|
||||||
|
-> DecodeResult<()>
|
||||||
|
{
|
||||||
|
// First load the directory, which maps the def-ids found
|
||||||
|
// elsewhere into `DefPath`. We can then refresh the `DefPath` to
|
||||||
|
// obtain updated def-ids.
|
||||||
|
let directory = {
|
||||||
|
let directory_doc = reader::get_doc(doc, DIRECTORY_TAG);
|
||||||
|
let mut decoder = Decoder::new(directory_doc);
|
||||||
|
try!(DefIdDirectory::decode(&mut decoder))
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!("decode_dep_graph: directory = {:#?}", directory);
|
||||||
|
|
||||||
|
// Retrace those paths to find their current location (if any).
|
||||||
|
let retraced = directory.retrace(tcx);
|
||||||
|
|
||||||
|
debug!("decode_dep_graph: retraced = {:#?}", retraced);
|
||||||
|
|
||||||
|
// Deserialize the dep-graph (which will include DefPathIndex entries)
|
||||||
|
let serialized_dep_graph = {
|
||||||
|
let dep_graph_doc = reader::get_doc(doc, DEP_GRAPH_TAG);
|
||||||
|
let mut decoder = Decoder::new(dep_graph_doc);
|
||||||
|
try!(SerializedDepGraph::decode(&mut decoder))
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!("decode_dep_graph: serialized_dep_graph = {:#?}", serialized_dep_graph);
|
||||||
|
|
||||||
|
// Compute the set of Hir nodes whose data has changed.
|
||||||
|
let mut dirty_nodes =
|
||||||
|
initial_dirty_nodes(tcx, &serialized_dep_graph.hashes, &retraced);
|
||||||
|
|
||||||
|
debug!("decode_dep_graph: initial dirty_nodes = {:#?}", dirty_nodes);
|
||||||
|
|
||||||
|
// Find all DepNodes reachable from that core set. This loop
|
||||||
|
// iterates repeatedly over the list of edges whose source is not
|
||||||
|
// known to be dirty (`clean_edges`). If it finds an edge whose
|
||||||
|
// source is dirty, it removes it from that list and adds the
|
||||||
|
// target to `dirty_nodes`. It stops when it reaches a fixed
|
||||||
|
// point.
|
||||||
|
let clean_edges = compute_clean_edges(&serialized_dep_graph.edges,
|
||||||
|
&retraced,
|
||||||
|
&mut dirty_nodes);
|
||||||
|
|
||||||
|
// Add synthetic `foo->foo` edges for each clean node `foo` that
|
||||||
|
// we had before. This is sort of a hack to create clean nodes in
|
||||||
|
// the graph, since the existence of a node is a signal that the
|
||||||
|
// work it represents need not be repeated.
|
||||||
|
let clean_nodes =
|
||||||
|
serialized_dep_graph.nodes
|
||||||
|
.iter()
|
||||||
|
.filter_map(|&node| retraced.map(node))
|
||||||
|
.filter(|node| !dirty_nodes.contains(node))
|
||||||
|
.map(|node| (node, node));
|
||||||
|
|
||||||
|
// Add nodes and edges that are not dirty into our main graph.
|
||||||
|
let dep_graph = tcx.dep_graph.clone();
|
||||||
|
for (source, target) in clean_edges.into_iter().chain(clean_nodes) {
|
||||||
|
let _task = dep_graph.in_task(target);
|
||||||
|
dep_graph.read(source);
|
||||||
|
|
||||||
|
debug!("decode_dep_graph: clean edge: {:?} -> {:?}", source, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initial_dirty_nodes<'tcx>(tcx: &ty::TyCtxt<'tcx>,
|
||||||
|
hashed_items: &[SerializedHash],
|
||||||
|
retraced: &RetracedDefIdDirectory)
|
||||||
|
-> DirtyNodes {
|
||||||
|
let mut items_removed = false;
|
||||||
|
let mut dirty_nodes = FnvHashSet();
|
||||||
|
for hashed_item in hashed_items {
|
||||||
|
match retraced.def_id(hashed_item.index) {
|
||||||
|
Some(def_id) => {
|
||||||
|
let current_hash = tcx.calculate_item_hash(def_id);
|
||||||
|
debug!("initial_dirty_nodes: hash of {:?} is {:?}, was {:?}",
|
||||||
|
def_id, current_hash, hashed_item.hash);
|
||||||
|
if current_hash != hashed_item.hash {
|
||||||
|
dirty_nodes.insert(DepNode::Hir(def_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
items_removed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any of the items in the krate have changed, then we consider
|
||||||
|
// the meta-node `Krate` to be dirty, since that means something
|
||||||
|
// which (potentially) read the contents of every single item.
|
||||||
|
if items_removed || !dirty_nodes.is_empty() {
|
||||||
|
dirty_nodes.insert(DepNode::Krate);
|
||||||
|
}
|
||||||
|
|
||||||
|
dirty_nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compute_clean_edges(serialized_edges: &[(SerializedEdge)],
|
||||||
|
retraced: &RetracedDefIdDirectory,
|
||||||
|
dirty_nodes: &mut DirtyNodes)
|
||||||
|
-> CleanEdges {
|
||||||
|
// Build up an initial list of edges. Include an edge (source,
|
||||||
|
// target) if neither node has been removed. If the source has
|
||||||
|
// been removed, add target to the list of dirty nodes.
|
||||||
|
let mut clean_edges = Vec::with_capacity(serialized_edges.len());
|
||||||
|
for &(serialized_source, serialized_target) in serialized_edges {
|
||||||
|
if let Some(target) = retraced.map(serialized_target) {
|
||||||
|
if let Some(source) = retraced.map(serialized_source) {
|
||||||
|
clean_edges.push((source, target))
|
||||||
|
} else {
|
||||||
|
// source removed, target must be dirty
|
||||||
|
dirty_nodes.insert(target);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// target removed, ignore the edge
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("compute_clean_edges: dirty_nodes={:#?}", dirty_nodes);
|
||||||
|
|
||||||
|
// Propagate dirty marks by iterating repeatedly over
|
||||||
|
// `clean_edges`. If we find an edge `(source, target)` where
|
||||||
|
// `source` is dirty, add `target` to the list of dirty nodes and
|
||||||
|
// remove it. Keep doing this until we find no more dirty nodes.
|
||||||
|
let mut previous_size = 0;
|
||||||
|
while dirty_nodes.len() > previous_size {
|
||||||
|
debug!("compute_clean_edges: previous_size={}", previous_size);
|
||||||
|
previous_size = dirty_nodes.len();
|
||||||
|
let mut i = 0;
|
||||||
|
while i < clean_edges.len() {
|
||||||
|
if dirty_nodes.contains(&clean_edges[i].0) {
|
||||||
|
let (source, target) = clean_edges.swap_remove(i);
|
||||||
|
debug!("compute_clean_edges: dirty source {:?} -> {:?}",
|
||||||
|
source, target);
|
||||||
|
dirty_nodes.insert(target);
|
||||||
|
} else if dirty_nodes.contains(&clean_edges[i].1) {
|
||||||
|
let (source, target) = clean_edges.swap_remove(i);
|
||||||
|
debug!("compute_clean_edges: dirty target {:?} -> {:?}",
|
||||||
|
source, target);
|
||||||
|
} else {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clean_edges
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT
|
||||||
|
// file at the top-level directory of this distribution and at
|
||||||
|
// http://rust-lang.org/COPYRIGHT.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||||
|
// option. This file may not be copied, modified, or distributed
|
||||||
|
// except according to those terms.
|
||||||
|
|
||||||
|
//! When in incremental mode, this pass dumps out the dependency graph
|
||||||
|
//! into the given directory. At the same time, it also hashes the
|
||||||
|
//! various HIR nodes.
|
||||||
|
|
||||||
|
mod data;
|
||||||
|
mod directory;
|
||||||
|
mod dirty_clean;
|
||||||
|
mod load;
|
||||||
|
mod save;
|
||||||
|
mod util;
|
||||||
|
|
||||||
|
pub use self::load::load_dep_graph;
|
||||||
|
pub use self::save::save_dep_graph;
|
|
@ -0,0 +1,142 @@
|
||||||
|
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
|
||||||
|
// file at the top-level directory of this distribution and at
|
||||||
|
// http://rust-lang.org/COPYRIGHT.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||||
|
// option. This file may not be copied, modified, or distributed
|
||||||
|
// except according to those terms.
|
||||||
|
|
||||||
|
use calculate_svh::SvhCalculate;
|
||||||
|
use rbml::writer::{EncodeResult, Encoder};
|
||||||
|
use rustc::dep_graph::DepNode;
|
||||||
|
use rustc::ty;
|
||||||
|
use rustc_serialize::{Encodable as RustcEncodable};
|
||||||
|
use std::io::{Cursor, Write};
|
||||||
|
use std::fs::{self, File};
|
||||||
|
|
||||||
|
use super::data::*;
|
||||||
|
use super::directory::*;
|
||||||
|
use super::util::*;
|
||||||
|
|
||||||
|
pub fn save_dep_graph<'tcx>(tcx: &ty::TyCtxt<'tcx>) {
|
||||||
|
let _ignore = tcx.dep_graph.in_ignore();
|
||||||
|
|
||||||
|
if let Some(dep_graph) = dep_graph_path(tcx) {
|
||||||
|
// delete the old dep-graph, if any
|
||||||
|
if dep_graph.exists() {
|
||||||
|
match fs::remove_file(&dep_graph) {
|
||||||
|
Ok(()) => { }
|
||||||
|
Err(err) => {
|
||||||
|
tcx.sess.err(
|
||||||
|
&format!("unable to delete old dep-graph at `{}`: {}",
|
||||||
|
dep_graph.display(), err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate the data in a memory buffer
|
||||||
|
let mut wr = Cursor::new(Vec::new());
|
||||||
|
match encode_dep_graph(tcx, &mut Encoder::new(&mut wr)) {
|
||||||
|
Ok(()) => { }
|
||||||
|
Err(err) => {
|
||||||
|
tcx.sess.err(
|
||||||
|
&format!("could not encode dep-graph to `{}`: {}",
|
||||||
|
dep_graph.display(), err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// write the data out
|
||||||
|
let data = wr.into_inner();
|
||||||
|
match
|
||||||
|
File::create(&dep_graph)
|
||||||
|
.and_then(|mut file| file.write_all(&data))
|
||||||
|
{
|
||||||
|
Ok(_) => { }
|
||||||
|
Err(err) => {
|
||||||
|
tcx.sess.err(
|
||||||
|
&format!("failed to write dep-graph to `{}`: {}",
|
||||||
|
dep_graph.display(), err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encode_dep_graph<'tcx>(tcx: &ty::TyCtxt<'tcx>,
|
||||||
|
encoder: &mut Encoder)
|
||||||
|
-> EncodeResult
|
||||||
|
{
|
||||||
|
// Here we take advantage of how RBML allows us to skip around
|
||||||
|
// and encode the depgraph as a two-part structure:
|
||||||
|
//
|
||||||
|
// ```
|
||||||
|
// <dep-graph>[SerializedDepGraph]</dep-graph> // tag 0
|
||||||
|
// <directory>[DefIdDirectory]</directory> // tag 1
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// Then later we can load the directory by skipping to find tag 1.
|
||||||
|
|
||||||
|
let query = tcx.dep_graph.query();
|
||||||
|
|
||||||
|
let mut builder = DefIdDirectoryBuilder::new(tcx);
|
||||||
|
|
||||||
|
// Create hashes for things we can persist.
|
||||||
|
let hashes =
|
||||||
|
query.nodes()
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|dep_node| match dep_node {
|
||||||
|
DepNode::Hir(def_id) => {
|
||||||
|
assert!(def_id.is_local());
|
||||||
|
builder.add(def_id)
|
||||||
|
.map(|index| {
|
||||||
|
let hash = tcx.calculate_item_hash(def_id);
|
||||||
|
SerializedHash { index: index, hash: hash }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => None
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Create the serialized dep-graph, dropping nodes that are
|
||||||
|
// from other crates or from inlined items.
|
||||||
|
//
|
||||||
|
// FIXME(#32015) fix handling of other crates
|
||||||
|
let graph = SerializedDepGraph {
|
||||||
|
nodes: query.nodes().into_iter()
|
||||||
|
.flat_map(|node| builder.map(node))
|
||||||
|
.collect(),
|
||||||
|
edges: query.edges().into_iter()
|
||||||
|
.flat_map(|(source_node, target_node)| {
|
||||||
|
builder.map(source_node)
|
||||||
|
.and_then(|source| {
|
||||||
|
builder.map(target_node)
|
||||||
|
.map(|target| (source, target))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
hashes: hashes,
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!("graph = {:#?}", graph);
|
||||||
|
|
||||||
|
// Encode the graph data into RBML.
|
||||||
|
try!(encoder.start_tag(DEP_GRAPH_TAG));
|
||||||
|
try!(graph.encode(encoder));
|
||||||
|
try!(encoder.end_tag());
|
||||||
|
|
||||||
|
// Now encode the directory.
|
||||||
|
let directory = builder.into_directory();
|
||||||
|
|
||||||
|
debug!("directory = {:#?}", directory);
|
||||||
|
|
||||||
|
try!(encoder.start_tag(DIRECTORY_TAG));
|
||||||
|
try!(directory.encode(encoder));
|
||||||
|
try!(encoder.end_tag());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
|
||||||
|
// file at the top-level directory of this distribution and at
|
||||||
|
// http://rust-lang.org/COPYRIGHT.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||||
|
// option. This file may not be copied, modified, or distributed
|
||||||
|
// except according to those terms.
|
||||||
|
|
||||||
|
use rustc::ty;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
pub const DEP_GRAPH_TAG: usize = 0x100;
|
||||||
|
|
||||||
|
pub const DIRECTORY_TAG: usize = DEP_GRAPH_TAG + 1;
|
||||||
|
|
||||||
|
pub fn dep_graph_path<'tcx>(tcx: &ty::TyCtxt<'tcx>) -> Option<PathBuf> {
|
||||||
|
// For now, just save/load dep-graph from
|
||||||
|
// directory/dep_graph.rbml
|
||||||
|
tcx.sess.opts.incremental.as_ref().and_then(|incr_dir| {
|
||||||
|
match fs::create_dir_all(&incr_dir){
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(err) => {
|
||||||
|
tcx.sess.err(
|
||||||
|
&format!("could not create the directory `{}`: {}",
|
||||||
|
incr_dir.display(), err));
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(incr_dir.join("dep_graph.rbml"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -356,6 +356,14 @@ pub const KNOWN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeGat
|
||||||
"the `#[rustc_if_this_changed]` attribute \
|
"the `#[rustc_if_this_changed]` attribute \
|
||||||
is just used for rustc unit tests \
|
is just used for rustc unit tests \
|
||||||
and will never be stable")),
|
and will never be stable")),
|
||||||
|
("rustc_dirty", Whitelisted, Gated("rustc_attrs",
|
||||||
|
"the `#[rustc_dirty]` attribute \
|
||||||
|
is just used for rustc unit tests \
|
||||||
|
and will never be stable")),
|
||||||
|
("rustc_clean", Whitelisted, Gated("rustc_attrs",
|
||||||
|
"the `#[rustc_clean]` attribute \
|
||||||
|
is just used for rustc unit tests \
|
||||||
|
and will never be stable")),
|
||||||
("rustc_symbol_name", Whitelisted, Gated("rustc_attrs",
|
("rustc_symbol_name", Whitelisted, Gated("rustc_attrs",
|
||||||
"internal rustc attributes will never be stable")),
|
"internal rustc attributes will never be stable")),
|
||||||
("rustc_item_path", Whitelisted, Gated("rustc_attrs",
|
("rustc_item_path", Whitelisted, Gated("rustc_attrs",
|
||||||
|
|
Loading…
Reference in New Issue