add code to persist graph and for unit-testing

This commit is contained in:
Niko Matsakis 2016-03-28 17:42:39 -04:00
parent d8263c4758
commit 3fb40c1d95
10 changed files with 742 additions and 0 deletions

View File

@ -829,6 +829,10 @@ pub fn phase_3_run_analysis_passes<'tcx, F, R>(sess: &'tcx Session,
index,
name,
|tcx| {
time(time_passes,
"load_dep_graph",
|| rustc_incremental::load_dep_graph(tcx));
// passes are timed inside typeck
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",
move || rustc_incremental::assert_dep_graph(tcx));
time(time_passes,
"serialize dep graph",
move || rustc_incremental::save_dep_graph(tcx));
translation
}

View File

@ -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,
}

View File

@ -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()
}
}

View File

@ -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));
}
}
}
}
}

View File

@ -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
}

View File

@ -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;

View File

@ -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(())
}

View File

@ -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"))
})
}

View File

@ -356,6 +356,14 @@ pub const KNOWN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeGat
"the `#[rustc_if_this_changed]` attribute \
is just used for rustc unit tests \
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",
"internal rustc attributes will never be stable")),
("rustc_item_path", Whitelisted, Gated("rustc_attrs",