about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/librustc_incremental/assert_dep_graph.rs10
-rw-r--r--src/librustc_incremental/calculate_svh/mod.rs40
-rw-r--r--src/librustc_incremental/calculate_svh/svh_visitor.rs12
-rw-r--r--src/librustc_incremental/lib.rs7
-rw-r--r--src/librustc_incremental/persist/data.rs13
-rw-r--r--src/librustc_incremental/persist/directory.rs1
-rw-r--r--src/librustc_incremental/persist/dirty_clean.rs192
-rw-r--r--src/librustc_incremental/persist/load.rs56
-rw-r--r--src/librustc_incremental/persist/save.rs79
-rw-r--r--src/libsyntax/feature_gate.rs10
-rw-r--r--src/test/incremental/hashes/struct_defs.rs238
-rw-r--r--src/tools/compiletest/src/header.rs50
-rw-r--r--src/tools/compiletest/src/runtest.rs29
13 files changed, 622 insertions, 115 deletions
diff --git a/src/librustc_incremental/assert_dep_graph.rs b/src/librustc_incremental/assert_dep_graph.rs
index b28454cddb2..28aab1fdd41 100644
--- a/src/librustc_incremental/assert_dep_graph.rs
+++ b/src/librustc_incremental/assert_dep_graph.rs
@@ -59,9 +59,7 @@ use std::io::Write;
 use syntax::ast;
 use syntax::parse::token::InternedString;
 use syntax_pos::Span;
-
-const IF_THIS_CHANGED: &'static str = "rustc_if_this_changed";
-const THEN_THIS_WOULD_NEED: &'static str = "rustc_then_this_would_need";
+use {ATTR_IF_THIS_CHANGED, ATTR_THEN_THIS_WOULD_NEED};
 
 pub fn assert_dep_graph<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>) {
     let _ignore = tcx.dep_graph.in_ignore();
@@ -91,7 +89,7 @@ pub fn assert_dep_graph<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>) {
         assert!(tcx.sess.opts.debugging_opts.query_dep_graph,
                 "cannot use the `#[{}]` or `#[{}]` annotations \
                  without supplying `-Z query-dep-graph`",
-                IF_THIS_CHANGED, THEN_THIS_WOULD_NEED);
+                ATTR_IF_THIS_CHANGED, ATTR_THEN_THIS_WOULD_NEED);
     }
 
     // Check paths.
@@ -125,7 +123,7 @@ impl<'a, 'tcx> IfThisChanged<'a, 'tcx> {
     fn process_attrs(&mut self, node_id: ast::NodeId, attrs: &[ast::Attribute]) {
         let def_id = self.tcx.map.local_def_id(node_id);
         for attr in attrs {
-            if attr.check_name(IF_THIS_CHANGED) {
+            if attr.check_name(ATTR_IF_THIS_CHANGED) {
                 let dep_node_interned = self.argument(attr);
                 let dep_node = match dep_node_interned {
                     None => DepNode::Hir(def_id),
@@ -141,7 +139,7 @@ impl<'a, 'tcx> IfThisChanged<'a, 'tcx> {
                     }
                 };
                 self.if_this_changed.push((attr.span, def_id, dep_node));
-            } else if attr.check_name(THEN_THIS_WOULD_NEED) {
+            } else if attr.check_name(ATTR_THEN_THIS_WOULD_NEED) {
                 let dep_node_interned = self.argument(attr);
                 let dep_node = match dep_node_interned {
                     Some(ref n) => {
diff --git a/src/librustc_incremental/calculate_svh/mod.rs b/src/librustc_incremental/calculate_svh/mod.rs
index c54fe211451..92ed2637c3d 100644
--- a/src/librustc_incremental/calculate_svh/mod.rs
+++ b/src/librustc_incremental/calculate_svh/mod.rs
@@ -28,6 +28,7 @@
 //! at the beginning.
 
 use syntax::ast;
+use std::cell::RefCell;
 use std::hash::{Hash, SipHasher, Hasher};
 use rustc::dep_graph::DepNode;
 use rustc::hir;
@@ -46,7 +47,42 @@ mod def_path_hash;
 mod svh_visitor;
 mod caching_codemap_view;
 
-pub type IncrementalHashesMap = FnvHashMap<DepNode<DefId>, u64>;
+pub struct IncrementalHashesMap {
+    hashes: FnvHashMap<DepNode<DefId>, u64>,
+
+    // These are the metadata hashes for the current crate as they were stored
+    // during the last compilation session. They are only loaded if
+    // -Z query-dep-graph was specified and are needed for auto-tests using
+    // the #[rustc_metadata_dirty] and #[rustc_metadata_clean] attributes to
+    // check whether some metadata hash has changed in between two revisions.
+    pub prev_metadata_hashes: RefCell<FnvHashMap<DefId, u64>>,
+}
+
+impl IncrementalHashesMap {
+    pub fn new() -> IncrementalHashesMap {
+        IncrementalHashesMap {
+            hashes: FnvHashMap(),
+            prev_metadata_hashes: RefCell::new(FnvHashMap()),
+        }
+    }
+
+    pub fn insert(&mut self, k: DepNode<DefId>, v: u64) -> Option<u64> {
+        self.hashes.insert(k, v)
+    }
+
+    pub fn iter<'a>(&'a self) -> ::std::collections::hash_map::Iter<'a, DepNode<DefId>, u64> {
+        self.hashes.iter()
+    }
+}
+
+impl<'a> ::std::ops::Index<&'a DepNode<DefId>> for IncrementalHashesMap {
+    type Output = u64;
+
+    fn index(&self, index: &'a DepNode<DefId>) -> &u64 {
+        &self.hashes[index]
+    }
+}
+
 
 pub fn compute_incremental_hashes_map<'a, 'tcx: 'a>(tcx: TyCtxt<'a, 'tcx, 'tcx>)
                                                     -> IncrementalHashesMap {
@@ -55,7 +91,7 @@ pub fn compute_incremental_hashes_map<'a, 'tcx: 'a>(tcx: TyCtxt<'a, 'tcx, 'tcx>)
     let hash_spans = tcx.sess.opts.debuginfo != NoDebugInfo;
     let mut visitor = HashItemsVisitor {
         tcx: tcx,
-        hashes: FnvHashMap(),
+        hashes: IncrementalHashesMap::new(),
         def_path_hashes: DefPathHashes::new(tcx),
         codemap: CachingCodemapView::new(tcx),
         hash_spans: hash_spans,
diff --git a/src/librustc_incremental/calculate_svh/svh_visitor.rs b/src/librustc_incremental/calculate_svh/svh_visitor.rs
index 9950f470a82..55fe5fc1e34 100644
--- a/src/librustc_incremental/calculate_svh/svh_visitor.rs
+++ b/src/librustc_incremental/calculate_svh/svh_visitor.rs
@@ -30,9 +30,15 @@ use std::hash::{Hash, SipHasher};
 use super::def_path_hash::DefPathHashes;
 use super::caching_codemap_view::CachingCodemapView;
 
-const IGNORED_ATTRIBUTES: &'static [&'static str] = &["cfg",
-                                                      "rustc_clean",
-                                                      "rustc_dirty"];
+const IGNORED_ATTRIBUTES: &'static [&'static str] = &[
+    "cfg",
+    ::ATTR_IF_THIS_CHANGED,
+    ::ATTR_THEN_THIS_WOULD_NEED,
+    ::ATTR_DIRTY,
+    ::ATTR_CLEAN,
+    ::ATTR_DIRTY_METADATA,
+    ::ATTR_CLEAN_METADATA
+];
 
 pub struct StrictVersionHashVisitor<'a, 'hash: 'a, 'tcx: 'hash> {
     pub tcx: TyCtxt<'hash, 'tcx, 'tcx>,
diff --git a/src/librustc_incremental/lib.rs b/src/librustc_incremental/lib.rs
index 42b5657e212..2c1340e566d 100644
--- a/src/librustc_incremental/lib.rs
+++ b/src/librustc_incremental/lib.rs
@@ -35,6 +35,13 @@ extern crate serialize as rustc_serialize;
 #[macro_use] extern crate syntax;
 extern crate syntax_pos;
 
+const ATTR_DIRTY: &'static str = "rustc_dirty";
+const ATTR_CLEAN: &'static str = "rustc_clean";
+const ATTR_DIRTY_METADATA: &'static str = "rustc_metadata_dirty";
+const ATTR_CLEAN_METADATA: &'static str = "rustc_metadata_clean";
+const ATTR_IF_THIS_CHANGED: &'static str = "rustc_if_this_changed";
+const ATTR_THEN_THIS_WOULD_NEED: &'static str = "rustc_then_this_would_need";
+
 mod assert_dep_graph;
 mod calculate_svh;
 mod persist;
diff --git a/src/librustc_incremental/persist/data.rs b/src/librustc_incremental/persist/data.rs
index 12f3ed8ae2b..57e7a0bc21a 100644
--- a/src/librustc_incremental/persist/data.rs
+++ b/src/librustc_incremental/persist/data.rs
@@ -13,6 +13,7 @@
 use rustc::dep_graph::{DepNode, WorkProduct, WorkProductId};
 use rustc::hir::def_id::DefIndex;
 use std::sync::Arc;
+use rustc_data_structures::fnv::FnvHashMap;
 
 use super::directory::DefPathIndex;
 
@@ -93,6 +94,18 @@ pub struct SerializedMetadataHashes {
     /// a `DefPathIndex` that gets retracted to the current `DefId`
     /// (matching the one found in this structure).
     pub hashes: Vec<SerializedMetadataHash>,
+
+    /// For each DefIndex (as it occurs in SerializedMetadataHash), this
+    /// map stores the DefPathIndex (as it occurs in DefIdDirectory), so
+    /// that we can find the new DefId for a SerializedMetadataHash in a
+    /// subsequent compilation session.
+    ///
+    /// This map is only needed for running auto-tests using the
+    /// #[rustc_metadata_dirty] and #[rustc_metadata_clean] attributes, and
+    /// is only populated if -Z query-dep-graph is specified. It will be
+    /// empty otherwise. Importing crates are perfectly happy with just having
+    /// the DefIndex.
+    pub index_map: FnvHashMap<DefIndex, DefPathIndex>
 }
 
 /// The hash for some metadata that (when saving) will be exported
diff --git a/src/librustc_incremental/persist/directory.rs b/src/librustc_incremental/persist/directory.rs
index cca364f442d..619e237ee34 100644
--- a/src/librustc_incremental/persist/directory.rs
+++ b/src/librustc_incremental/persist/directory.rs
@@ -178,7 +178,6 @@ impl<'a,'tcx> DefIdDirectoryBuilder<'a,'tcx> {
         &self.directory.paths[id.index as usize]
     }
 
-
     pub fn map(&mut self, node: &DepNode<DefId>) -> DepNode<DefPathIndex> {
         node.map_def(|&def_id| Some(self.add(def_id))).unwrap()
     }
diff --git a/src/librustc_incremental/persist/dirty_clean.rs b/src/librustc_incremental/persist/dirty_clean.rs
index fda7ef207a3..95452021d87 100644
--- a/src/librustc_incremental/persist/dirty_clean.rs
+++ b/src/librustc_incremental/persist/dirty_clean.rs
@@ -9,10 +9,10 @@
 // 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:
+//! after it is loaded from disk and just after it has been saved.
+//! 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
@@ -23,6 +23,22 @@
 //!
 //! Errors are reported if we are in the suitable configuration but
 //! the required condition is not met.
+//!
+//! The `#[rustc_metadata_dirty]` and `#[rustc_metadata_clean]` attributes
+//! can be used to check the incremental compilation hash (ICH) values of
+//! metadata exported in rlibs.
+//!
+//! - If a node is marked with `#[rustc_metadata_clean(cfg="rev2")]` we
+//!   check that the metadata hash for that node is the same for "rev2"
+//!   it was for "rev1".
+//! - If a node is marked with `#[rustc_metadata_dirty(cfg="rev2")]` we
+//!   check that the metadata hash for that node is *different* for "rev2"
+//!   than it was for "rev1".
+//!
+//! Note that the metadata-testing attributes must never specify the
+//! first revision. This would lead to a crash since there is no
+//! previous revision to compare things to.
+//!
 
 use super::directory::RetracedDefIdDirectory;
 use super::load::DirtyNodes;
@@ -30,13 +46,14 @@ use rustc::dep_graph::{DepGraphQuery, DepNode};
 use rustc::hir;
 use rustc::hir::def_id::DefId;
 use rustc::hir::intravisit::Visitor;
-use rustc_data_structures::fnv::FnvHashSet;
 use syntax::ast::{self, Attribute, NestedMetaItem};
+use rustc_data_structures::fnv::{FnvHashSet, FnvHashMap};
 use syntax::parse::token::InternedString;
+use syntax_pos::Span;
 use rustc::ty::TyCtxt;
 
-const DIRTY: &'static str = "rustc_dirty";
-const CLEAN: &'static str = "rustc_clean";
+use {ATTR_DIRTY, ATTR_CLEAN, ATTR_DIRTY_METADATA, ATTR_CLEAN_METADATA};
+
 const LABEL: &'static str = "label";
 const CFG: &'static str = "cfg";
 
@@ -70,50 +87,11 @@ pub struct DirtyCleanVisitor<'a, 'tcx:'a> {
 }
 
 impl<'a, 'tcx> DirtyCleanVisitor<'a, 'tcx> {
-    fn expect_associated_value(&self, item: &NestedMetaItem) -> InternedString {
-        if let Some(value) = item.value_str() {
-            value
-        } else {
-            let msg = if let Some(name) = item.name() {
-                format!("associated value expected for `{}`", name)
-            } else {
-                "expected an associated value".to_string()
-            };
-
-            self.tcx.sess.span_fatal(item.span, &msg);
-        }
-    }
-
-    /// 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;
-                    }
-                }
-                return false;
-            }
-        }
-
-        self.tcx.sess.span_fatal(
-            attr.span,
-            &format!("no cfg attribute"));
-    }
 
     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);
+                let value = expect_associated_value(self.tcx, item);
                 match DepNode::from_label_string(&value[..], def_id) {
                     Ok(def_id) => return def_id,
                     Err(()) => {
@@ -194,12 +172,12 @@ 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) {
+            if attr.check_name(ATTR_DIRTY) {
+                if check_config(self.tcx, attr) {
                     self.assert_dirty(item, self.dep_node(attr, def_id));
                 }
-            } else if attr.check_name(CLEAN) {
-                if self.check_config(attr) {
+            } else if attr.check_name(ATTR_CLEAN) {
+                if check_config(self.tcx, attr) {
                     self.assert_clean(item, self.dep_node(attr, def_id));
                 }
             }
@@ -207,3 +185,115 @@ impl<'a, 'tcx> Visitor<'tcx> for DirtyCleanVisitor<'a, 'tcx> {
     }
 }
 
+pub fn check_dirty_clean_metadata<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
+                                  prev_metadata_hashes: &FnvHashMap<DefId, u64>,
+                                  current_metadata_hashes: &FnvHashMap<DefId, u64>) {
+    if !tcx.sess.opts.debugging_opts.query_dep_graph {
+        return;
+    }
+
+    tcx.dep_graph.with_ignore(||{
+        let krate = tcx.map.krate();
+        krate.visit_all_items(&mut DirtyCleanMetadataVisitor {
+            tcx: tcx,
+            prev_metadata_hashes: prev_metadata_hashes,
+            current_metadata_hashes: current_metadata_hashes,
+        });
+    });
+}
+
+pub struct DirtyCleanMetadataVisitor<'a, 'tcx:'a, 'm> {
+    tcx: TyCtxt<'a, 'tcx, 'tcx>,
+    prev_metadata_hashes: &'m FnvHashMap<DefId, u64>,
+    current_metadata_hashes: &'m FnvHashMap<DefId, u64>,
+}
+
+impl<'a, 'tcx, 'm> Visitor<'tcx> for DirtyCleanMetadataVisitor<'a, 'tcx, 'm> {
+    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(ATTR_DIRTY_METADATA) {
+                if check_config(self.tcx, attr) {
+                    self.assert_state(false, def_id, item.span);
+                }
+            } else if attr.check_name(ATTR_CLEAN_METADATA) {
+                if check_config(self.tcx, attr) {
+                    self.assert_state(true, def_id, item.span);
+                }
+            }
+        }
+    }
+}
+
+impl<'a, 'tcx, 'm> DirtyCleanMetadataVisitor<'a, 'tcx, 'm> {
+
+    fn assert_state(&self, should_be_clean: bool, def_id: DefId, span: Span) {
+        let item_path = self.tcx.item_path_str(def_id);
+        debug!("assert_state({})", item_path);
+
+        if let Some(&prev_hash) = self.prev_metadata_hashes.get(&def_id) {
+            let hashes_are_equal = prev_hash == self.current_metadata_hashes[&def_id];
+
+            if should_be_clean && !hashes_are_equal {
+                self.tcx.sess.span_err(
+                        span,
+                        &format!("Metadata hash of `{}` is dirty, but should be clean",
+                                 item_path));
+            }
+
+            let should_be_dirty = !should_be_clean;
+            if should_be_dirty && hashes_are_equal {
+                self.tcx.sess.span_err(
+                        span,
+                        &format!("Metadata hash of `{}` is clean, but should be dirty",
+                                 item_path));
+            }
+        } else {
+            self.tcx.sess.span_err(
+                        span,
+                        &format!("Could not find previous metadata hash of `{}`",
+                                 item_path));
+        }
+    }
+}
+
+/// 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(tcx: TyCtxt, attr: &ast::Attribute) -> bool {
+    debug!("check_config(attr={:?})", attr);
+    let config = &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 = expect_associated_value(tcx, item);
+            debug!("check_config: searching for cfg {:?}", value);
+            for cfg in &config[..] {
+                if cfg.check_name(&value[..]) {
+                    debug!("check_config: matched {:?}", cfg);
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    tcx.sess.span_fatal(
+        attr.span,
+        &format!("no cfg attribute"));
+}
+
+fn expect_associated_value(tcx: TyCtxt, item: &NestedMetaItem) -> InternedString {
+    if let Some(value) = item.value_str() {
+        value
+    } else {
+        let msg = if let Some(name) = item.name() {
+            format!("associated value expected for `{}`", name)
+        } else {
+            "expected an associated value".to_string()
+        };
+
+        tcx.sess.span_fatal(item.span, &msg);
+    }
+}
diff --git a/src/librustc_incremental/persist/load.rs b/src/librustc_incremental/persist/load.rs
index b051e6c5ab7..ba15529c81a 100644
--- a/src/librustc_incremental/persist/load.rs
+++ b/src/librustc_incremental/persist/load.rs
@@ -12,9 +12,10 @@
 
 use rustc::dep_graph::DepNode;
 use rustc::hir::def_id::DefId;
+use rustc::hir::svh::Svh;
 use rustc::session::Session;
 use rustc::ty::TyCtxt;
-use rustc_data_structures::fnv::FnvHashSet;
+use rustc_data_structures::fnv::{FnvHashSet, FnvHashMap};
 use rustc_serialize::Decodable as RustcDecodable;
 use rustc_serialize::opaque::Decoder;
 use std::io::Read;
@@ -224,6 +225,9 @@ pub fn decode_dep_graph<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
 
     dirty_clean::check_dirty_clean_annotations(tcx, &dirty_raw_source_nodes, &retraced);
 
+    load_prev_metadata_hashes(tcx,
+                              &retraced,
+                              &mut *incremental_hashes_map.prev_metadata_hashes.borrow_mut());
     Ok(())
 }
 
@@ -241,6 +245,9 @@ fn dirty_nodes<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
         if let Some(dep_node) = retraced.map(&hash.dep_node) {
             let current_hash = hcx.hash(&dep_node).unwrap();
             if current_hash == hash.hash {
+                debug!("initial_dirty_nodes: {:?} is clean (hash={:?})",
+                   dep_node.map_def(|&def_id| Some(tcx.def_path(def_id))).unwrap(),
+                   current_hash);
                 continue;
             }
             debug!("initial_dirty_nodes: {:?} is dirty as hash is {:?}, was {:?}",
@@ -304,3 +311,50 @@ fn delete_dirty_work_product(tcx: TyCtxt,
         }
     }
 }
+
+fn load_prev_metadata_hashes(tcx: TyCtxt,
+                             retraced: &RetracedDefIdDirectory,
+                             output: &mut FnvHashMap<DefId, u64>) {
+    if !tcx.sess.opts.debugging_opts.query_dep_graph {
+        return
+    }
+
+    debug!("load_prev_metadata_hashes() - Loading previous metadata hashes");
+
+    let file_path = metadata_hash_export_path(tcx.sess);
+
+    if !file_path.exists() {
+        debug!("load_prev_metadata_hashes() - Couldn't find file containing \
+                hashes at `{}`", file_path.display());
+        return
+    }
+
+    debug!("load_prev_metadata_hashes() - File: {}", file_path.display());
+
+    let mut data = vec![];
+    if !File::open(&file_path)
+             .and_then(|mut file| file.read_to_end(&mut data)).is_ok() {
+        debug!("load_prev_metadata_hashes() - Couldn't read file containing \
+                hashes at `{}`", file_path.display());
+        return
+    }
+
+    debug!("load_prev_metadata_hashes() - Decoding hashes");
+    let mut decoder = Decoder::new(&mut data, 0);
+    let _ = Svh::decode(&mut decoder).unwrap();
+    let serialized_hashes = SerializedMetadataHashes::decode(&mut decoder).unwrap();
+
+    debug!("load_prev_metadata_hashes() - Mapping DefIds");
+
+    assert_eq!(serialized_hashes.index_map.len(), serialized_hashes.hashes.len());
+    for serialized_hash in serialized_hashes.hashes {
+        let def_path_index = serialized_hashes.index_map[&serialized_hash.def_index];
+        if let Some(def_id) = retraced.def_id(def_path_index) {
+            let old = output.insert(def_id, serialized_hash.hash);
+            assert!(old.is_none(), "already have hash for {:?}", def_id);
+        }
+    }
+
+    debug!("load_prev_metadata_hashes() - successfully loaded {} hashes",
+           serialized_hashes.index_map.len());
+}
diff --git a/src/librustc_incremental/persist/save.rs b/src/librustc_incremental/persist/save.rs
index 5b45874840f..896e8a9845e 100644
--- a/src/librustc_incremental/persist/save.rs
+++ b/src/librustc_incremental/persist/save.rs
@@ -27,6 +27,7 @@ use super::directory::*;
 use super::hash::*;
 use super::preds::*;
 use super::fs::*;
+use super::dirty_clean;
 
 pub fn save_dep_graph<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
                                 incremental_hashes_map: &IncrementalHashesMap,
@@ -37,16 +38,32 @@ pub fn save_dep_graph<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
     if sess.opts.incremental.is_none() {
         return;
     }
-    let mut hcx = HashContext::new(tcx, incremental_hashes_map);
+
     let mut builder = DefIdDirectoryBuilder::new(tcx);
     let query = tcx.dep_graph.query();
+    let mut hcx = HashContext::new(tcx, incremental_hashes_map);
     let preds = Predecessors::new(&query, &mut hcx);
+    let mut current_metadata_hashes = FnvHashMap();
+
+    // IMPORTANT: We are saving the metadata hashes *before* the dep-graph,
+    //            since metadata-encoding might add new entries to the
+    //            DefIdDirectory (which is saved in the dep-graph file).
+    save_in(sess,
+            metadata_hash_export_path(sess),
+            |e| encode_metadata_hashes(tcx,
+                                       svh,
+                                       &preds,
+                                       &mut builder,
+                                       &mut current_metadata_hashes,
+                                       e));
     save_in(sess,
             dep_graph_path(sess),
             |e| encode_dep_graph(&preds, &mut builder, e));
-    save_in(sess,
-            metadata_hash_export_path(sess),
-            |e| encode_metadata_hashes(tcx, svh, &preds, &mut builder, e));
+
+    let prev_metadata_hashes = incremental_hashes_map.prev_metadata_hashes.borrow();
+    dirty_clean::check_dirty_clean_metadata(tcx,
+                                            &*prev_metadata_hashes,
+                                            &current_metadata_hashes);
 }
 
 pub fn save_work_products(sess: &Session) {
@@ -63,13 +80,17 @@ pub fn save_work_products(sess: &Session) {
 fn save_in<F>(sess: &Session, path_buf: PathBuf, encode: F)
     where F: FnOnce(&mut Encoder) -> io::Result<()>
 {
+    debug!("save: storing data in {}", path_buf.display());
+
     // delete the old dep-graph, if any
     // Note: It's important that we actually delete the old file and not just
     // truncate and overwrite it, since it might be a shared hard-link, the
     // underlying data of which we don't want to modify
     if path_buf.exists() {
         match fs::remove_file(&path_buf) {
-            Ok(()) => {}
+            Ok(()) => {
+                debug!("save: remove old file");
+            }
             Err(err) => {
                 sess.err(&format!("unable to delete old dep-graph at `{}`: {}",
                                   path_buf.display(),
@@ -94,7 +115,9 @@ fn save_in<F>(sess: &Session, path_buf: PathBuf, encode: F)
     // write the data out
     let data = wr.into_inner();
     match File::create(&path_buf).and_then(|mut file| file.write_all(&data)) {
-        Ok(_) => {}
+        Ok(_) => {
+            debug!("save: data written to disk successfully");
+        }
         Err(err) => {
             sess.err(&format!("failed to write dep-graph to `{}`: {}",
                               path_buf.display(),
@@ -159,18 +182,9 @@ pub fn encode_metadata_hashes(tcx: TyCtxt,
                               svh: Svh,
                               preds: &Predecessors,
                               builder: &mut DefIdDirectoryBuilder,
+                              current_metadata_hashes: &mut FnvHashMap<DefId, u64>,
                               encoder: &mut Encoder)
                               -> io::Result<()> {
-    let mut def_id_hashes = FnvHashMap();
-    let mut def_id_hash = |def_id: DefId| -> u64 {
-        *def_id_hashes.entry(def_id)
-            .or_insert_with(|| {
-                let index = builder.add(def_id);
-                let path = builder.lookup_def_path(index);
-                path.deterministic_hash(tcx)
-            })
-    };
-
     // For each `MetaData(X)` node where `X` is local, accumulate a
     // hash.  These are the metadata items we export. Downstream
     // crates will want to see a hash that tells them whether we might
@@ -178,7 +192,13 @@ pub fn encode_metadata_hashes(tcx: TyCtxt,
     // compiled.
     //
     // (I initially wrote this with an iterator, but it seemed harder to read.)
-    let mut serialized_hashes = SerializedMetadataHashes { hashes: vec![] };
+    let mut serialized_hashes = SerializedMetadataHashes {
+        hashes: vec![],
+        index_map: FnvHashMap()
+    };
+
+    let mut def_id_hashes = FnvHashMap();
+
     for (&target, sources) in &preds.inputs {
         let def_id = match *target {
             DepNode::MetaData(def_id) => {
@@ -188,6 +208,15 @@ pub fn encode_metadata_hashes(tcx: TyCtxt,
             _ => continue,
         };
 
+        let mut def_id_hash = |def_id: DefId| -> u64 {
+            *def_id_hashes.entry(def_id)
+                .or_insert_with(|| {
+                    let index = builder.add(def_id);
+                    let path = builder.lookup_def_path(index);
+                    path.deterministic_hash(tcx)
+                })
+        };
+
         // To create the hash for each item `X`, we don't hash the raw
         // bytes of the metadata (though in principle we
         // could). Instead, we walk the predecessors of `MetaData(X)`
@@ -221,6 +250,22 @@ pub fn encode_metadata_hashes(tcx: TyCtxt,
         });
     }
 
+    if tcx.sess.opts.debugging_opts.query_dep_graph {
+        for serialized_hash in &serialized_hashes.hashes {
+            let def_id = DefId::local(serialized_hash.def_index);
+
+            // Store entry in the index_map
+            let def_path_index = builder.add(def_id);
+            serialized_hashes.index_map.insert(def_id.index, def_path_index);
+
+            // Record hash in current_metadata_hashes
+            current_metadata_hashes.insert(def_id, serialized_hash.hash);
+        }
+
+        debug!("save: stored index_map (len={}) for serialized hashes",
+               serialized_hashes.index_map.len());
+    }
+
     // Encode everything.
     svh.encode(encoder)?;
     serialized_hashes.encode(encoder)?;
diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs
index 27b97a0ad66..75cfa587ab1 100644
--- a/src/libsyntax/feature_gate.rs
+++ b/src/libsyntax/feature_gate.rs
@@ -508,6 +508,16 @@ pub const KNOWN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeGat
                                         is just used for rustc unit tests \
                                         and will never be stable",
                                        cfg_fn!(rustc_attrs))),
+    ("rustc_metadata_dirty", Whitelisted, Gated("rustc_attrs",
+                                                "the `#[rustc_metadata_dirty]` attribute \
+                                                 is just used for rustc unit tests \
+                                                 and will never be stable",
+                                                 cfg_fn!(rustc_attrs))),
+    ("rustc_metadata_clean", Whitelisted, Gated("rustc_attrs",
+                                                "the `#[rustc_metadata_clean]` attribute \
+                                                 is just used for rustc unit tests \
+                                                 and will never be stable",
+                                                 cfg_fn!(rustc_attrs))),
     ("rustc_partition_reused", Whitelisted, Gated("rustc_attrs",
                                                   "this attribute \
                                                    is just used for rustc unit tests \
diff --git a/src/test/incremental/hashes/struct_defs.rs b/src/test/incremental/hashes/struct_defs.rs
new file mode 100644
index 00000000000..74c7797be2a
--- /dev/null
+++ b/src/test/incremental/hashes/struct_defs.rs
@@ -0,0 +1,238 @@
+// Copyright 2016 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.
+
+
+// This test case tests the incremental compilation hash (ICH) implementation
+// for struct definitions.
+
+// The general pattern followed here is: Change one thing between rev1 and rev2
+// and make sure that the hash has changed, then change nothing between rev2 and
+// rev3 and make sure that the hash has not changed.
+
+// We also test the ICH for struct definitions exported in metadata. Same as
+// above, we want to make sure that the change between rev1 and rev2 also
+// results in a change of the ICH for the struct's metadata, and that it stays
+// the same between rev2 and rev3.
+
+// must-compile-successfully
+// revisions: cfail1 cfail2 cfail3
+// compile-flags: -Z query-dep-graph
+
+
+#![allow(warnings)]
+#![feature(rustc_attrs)]
+#![crate_type="rlib"]
+
+// Layout ----------------------------------------------------------------------
+#[cfg(cfail1)]
+pub struct LayoutPacked;
+
+#[cfg(not(cfail1))]
+#[rustc_dirty(label="Hir", cfg="cfail2")]
+#[rustc_clean(label="Hir", cfg="cfail3")]
+#[rustc_metadata_dirty(cfg="cfail2")]
+#[rustc_metadata_clean(cfg="cfail3")]
+#[repr(packed)]
+pub struct LayoutPacked;
+
+#[cfg(cfail1)]
+struct LayoutC;
+
+#[cfg(not(cfail1))]
+#[rustc_dirty(label="Hir", cfg="cfail2")]
+#[rustc_clean(label="Hir", cfg="cfail3")]
+#[rustc_metadata_dirty(cfg="cfail2")]
+#[rustc_metadata_clean(cfg="cfail3")]
+#[repr(C)]
+struct LayoutC;
+
+
+// Tuple Struct Change Field Type ----------------------------------------------
+
+#[cfg(cfail1)]
+struct TupleStructFieldType(i32);
+
+#[cfg(not(cfail1))]
+#[rustc_dirty(label="Hir", cfg="cfail2")]
+#[rustc_clean(label="Hir", cfg="cfail3")]
+#[rustc_metadata_dirty(cfg="cfail2")]
+#[rustc_metadata_clean(cfg="cfail3")]
+struct TupleStructFieldType(u32);
+
+
+// Tuple Struct Add Field ------------------------------------------------------
+
+#[cfg(cfail1)]
+struct TupleStructAddField(i32);
+
+#[cfg(not(cfail1))]
+#[rustc_dirty(label="Hir", cfg="cfail2")]
+#[rustc_clean(label="Hir", cfg="cfail3")]
+#[rustc_metadata_dirty(cfg="cfail2")]
+#[rustc_metadata_clean(cfg="cfail3")]
+struct TupleStructAddField(i32, u32);
+
+
+// Tuple Struct Field Visibility -----------------------------------------------
+
+#[cfg(cfail1)]
+struct TupleStructFieldVisibility(char);
+
+#[cfg(not(cfail1))]
+#[rustc_dirty(label="Hir", cfg="cfail2")]
+#[rustc_clean(label="Hir", cfg="cfail3")]
+#[rustc_metadata_dirty(cfg="cfail2")]
+#[rustc_metadata_clean(cfg="cfail3")]
+struct TupleStructFieldVisibility(pub char);
+
+
+// Record Struct Field Type ----------------------------------------------------
+
+#[cfg(cfail1)]
+struct RecordStructFieldType { x: f32 }
+
+#[cfg(not(cfail1))]
+#[rustc_dirty(label="Hir", cfg="cfail2")]
+#[rustc_clean(label="Hir", cfg="cfail3")]
+#[rustc_metadata_dirty(cfg="cfail2")]
+#[rustc_metadata_clean(cfg="cfail3")]
+struct RecordStructFieldType { x: u64 }
+
+
+// Record Struct Field Name ----------------------------------------------------
+
+#[cfg(cfail1)]
+struct RecordStructFieldName { x: f32 }
+
+#[cfg(not(cfail1))]
+#[rustc_dirty(label="Hir", cfg="cfail2")]
+#[rustc_clean(label="Hir", cfg="cfail3")]
+#[rustc_metadata_dirty(cfg="cfail2")]
+#[rustc_metadata_clean(cfg="cfail3")]
+struct RecordStructFieldName { y: f32 }
+
+
+// Record Struct Add Field -----------------------------------------------------
+
+#[cfg(cfail1)]
+struct RecordStructAddField { x: f32 }
+
+#[cfg(not(cfail1))]
+#[rustc_dirty(label="Hir", cfg="cfail2")]
+#[rustc_clean(label="Hir", cfg="cfail3")]
+#[rustc_metadata_dirty(cfg="cfail2")]
+#[rustc_metadata_clean(cfg="cfail3")]
+struct RecordStructAddField { x: f32, y: () }
+
+
+// Record Struct Field Visibility ----------------------------------------------
+
+#[cfg(cfail1)]
+struct RecordStructFieldVisibility { x: f32 }
+
+#[cfg(not(cfail1))]
+#[rustc_dirty(label="Hir", cfg="cfail2")]
+#[rustc_clean(label="Hir", cfg="cfail3")]
+#[rustc_metadata_dirty(cfg="cfail2")]
+#[rustc_metadata_clean(cfg="cfail3")]
+struct RecordStructFieldVisibility { pub x: f32 }
+
+
+// Add Lifetime Parameter ------------------------------------------------------
+
+#[cfg(cfail1)]
+struct AddLifetimeParameter<'a>(&'a f32, &'a f64);
+
+#[cfg(not(cfail1))]
+#[rustc_dirty(label="Hir", cfg="cfail2")]
+#[rustc_clean(label="Hir", cfg="cfail3")]
+#[rustc_metadata_dirty(cfg="cfail2")]
+#[rustc_metadata_clean(cfg="cfail3")]
+struct AddLifetimeParameter<'a, 'b>(&'a f32, &'b f64);
+
+
+// Add Lifetime Parameter Bound ------------------------------------------------
+
+#[cfg(cfail1)]
+struct AddLifetimeParameterBound<'a, 'b>(&'a f32, &'b f64);
+
+#[cfg(not(cfail1))]
+#[rustc_dirty(label="Hir", cfg="cfail2")]
+#[rustc_clean(label="Hir", cfg="cfail3")]
+#[rustc_metadata_dirty(cfg="cfail2")]
+#[rustc_metadata_clean(cfg="cfail3")]
+struct AddLifetimeParameterBound<'a, 'b: 'a>(&'a f32, &'b f64);
+
+#[cfg(cfail1)]
+struct AddLifetimeParameterBoundWhereClause<'a, 'b>(&'a f32, &'b f64);
+
+#[cfg(not(cfail1))]
+#[rustc_dirty(label="Hir", cfg="cfail2")]
+#[rustc_clean(label="Hir", cfg="cfail3")]
+#[rustc_metadata_dirty(cfg="cfail2")]
+#[rustc_metadata_clean(cfg="cfail3")]
+struct AddLifetimeParameterBoundWhereClause<'a, 'b>(&'a f32, &'b f64)
+    where 'b: 'a;
+
+
+// Add Type Parameter ----------------------------------------------------------
+
+#[cfg(cfail1)]
+struct AddTypeParameter<T1>(T1, T1);
+
+#[cfg(not(cfail1))]
+#[rustc_dirty(label="Hir", cfg="cfail2")]
+#[rustc_clean(label="Hir", cfg="cfail3")]
+#[rustc_metadata_dirty(cfg="cfail2")]
+#[rustc_metadata_clean(cfg="cfail3")]
+struct AddTypeParameter<T1, T2>(T1, T2);
+
+
+// Add Type Parameter Bound ----------------------------------------------------
+
+#[cfg(cfail1)]
+struct AddTypeParameterBound<T>(T);
+
+#[cfg(not(cfail1))]
+#[rustc_dirty(label="Hir", cfg="cfail2")]
+#[rustc_clean(label="Hir", cfg="cfail3")]
+#[rustc_metadata_dirty(cfg="cfail2")]
+#[rustc_metadata_clean(cfg="cfail3")]
+struct AddTypeParameterBound<T: Send>(T);
+
+
+#[cfg(cfail1)]
+struct AddTypeParameterBoundWhereClause<T>(T);
+
+#[cfg(not(cfail1))]
+#[rustc_dirty(label="Hir", cfg="cfail2")]
+#[rustc_clean(label="Hir", cfg="cfail3")]
+#[rustc_metadata_dirty(cfg="cfail2")]
+#[rustc_metadata_clean(cfg="cfail3")]
+struct AddTypeParameterBoundWhereClause<T>(T) where T: Sync;
+
+
+// Empty struct ----------------------------------------------------------------
+
+#[rustc_clean(label="Hir", cfg="cfail2")]
+#[rustc_metadata_clean(cfg="cfail2")]
+pub struct EmptyStruct;
+
+
+// Visibility ------------------------------------------------------------------
+
+#[cfg(cfail1)]
+struct Visibility;
+
+#[cfg(not(cfail1))]
+#[rustc_dirty(label="Hir", cfg="cfail2")]
+#[rustc_clean(label="Hir", cfg="cfail3")]
+#[rustc_metadata_clean(cfg="cfail3")]
+pub struct Visibility;
diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs
index 899a366a4bb..503a8516769 100644
--- a/src/tools/compiletest/src/header.rs
+++ b/src/tools/compiletest/src/header.rs
@@ -182,42 +182,32 @@ pub struct TestProps {
     // testing harness and used when generating compilation
     // arguments. (In particular, it propagates to the aux-builds.)
     pub incremental_dir: Option<PathBuf>,
+    // Specifies that a cfail test must actually compile without errors.
+    pub must_compile_successfully: bool,
 }
 
 impl TestProps {
     pub fn new() -> Self {
-        let error_patterns = Vec::new();
-        let aux_builds = Vec::new();
-        let exec_env = Vec::new();
-        let run_flags = None;
-        let pp_exact = None;
-        let check_lines = Vec::new();
-        let build_aux_docs = false;
-        let force_host = false;
-        let check_stdout = false;
-        let no_prefer_dynamic = false;
-        let pretty_expanded = false;
-        let pretty_compare_only = false;
-        let forbid_output = Vec::new();
         TestProps {
-            error_patterns: error_patterns,
+            error_patterns: vec![],
             compile_flags: vec![],
-            run_flags: run_flags,
-            pp_exact: pp_exact,
-            aux_builds: aux_builds,
+            run_flags: None,
+            pp_exact: None,
+            aux_builds: vec![],
             revisions: vec![],
             rustc_env: vec![],
-            exec_env: exec_env,
-            check_lines: check_lines,
-            build_aux_docs: build_aux_docs,
-            force_host: force_host,
-            check_stdout: check_stdout,
-            no_prefer_dynamic: no_prefer_dynamic,
-            pretty_expanded: pretty_expanded,
+            exec_env: vec![],
+            check_lines: vec![],
+            build_aux_docs: false,
+            force_host: false,
+            check_stdout: false,
+            no_prefer_dynamic: false,
+            pretty_expanded: false,
             pretty_mode: format!("normal"),
-            pretty_compare_only: pretty_compare_only,
-            forbid_output: forbid_output,
+            pretty_compare_only: false,
+            forbid_output: vec![],
             incremental_dir: None,
+            must_compile_successfully: false,
         }
     }
 
@@ -313,6 +303,10 @@ impl TestProps {
             if let Some(of) = parse_forbid_output(ln) {
                 self.forbid_output.push(of);
             }
+
+            if !self.must_compile_successfully {
+                self.must_compile_successfully = parse_must_compile_successfully(ln);
+            }
         });
 
         for key in vec!["RUST_TEST_NOCAPTURE", "RUST_TEST_THREADS"] {
@@ -420,6 +414,10 @@ fn parse_pretty_compare_only(line: &str) -> bool {
     parse_name_directive(line, "pretty-compare-only")
 }
 
+fn parse_must_compile_successfully(line: &str) -> bool {
+    parse_name_directive(line, "must-compile-successfully")
+}
+
 fn parse_env(line: &str, name: &str) -> Option<(String, String)> {
     parse_name_value_directive(line, name).map(|nv| {
         // nv is either FOO or FOO=BAR
diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs
index 34d86a65700..8fdb882164a 100644
--- a/src/tools/compiletest/src/runtest.rs
+++ b/src/tools/compiletest/src/runtest.rs
@@ -129,13 +129,21 @@ impl<'test> TestCx<'test> {
     fn run_cfail_test(&self) {
         let proc_res = self.compile_test();
 
-        if proc_res.status.success() {
-            self.fatal_proc_rec(
-                &format!("{} test compiled successfully!", self.config.mode)[..],
-                &proc_res);
-        }
+        if self.props.must_compile_successfully {
+            if !proc_res.status.success() {
+                self.fatal_proc_rec(
+                    "test compilation failed although it shouldn't!",
+                    &proc_res);
+            }
+        } else {
+            if proc_res.status.success() {
+                self.fatal_proc_rec(
+                    &format!("{} test compiled successfully!", self.config.mode)[..],
+                    &proc_res);
+            }
 
-        self.check_correct_failure_status(&proc_res);
+            self.check_correct_failure_status(&proc_res);
+        }
 
         let output_to_check = self.get_output(&proc_res);
         let expected_errors = errors::load_errors(&self.testpaths.file, self.revision);
@@ -147,6 +155,7 @@ impl<'test> TestCx<'test> {
         } else {
             self.check_error_patterns(&output_to_check, &proc_res);
         }
+
         self.check_no_compiler_crash(&proc_res);
         self.check_forbid_output(&output_to_check, &proc_res);
     }
@@ -943,8 +952,12 @@ actual:\n\
                             output_to_check: &str,
                             proc_res: &ProcRes) {
         if self.props.error_patterns.is_empty() {
-            self.fatal(&format!("no error pattern specified in {:?}",
-                                self.testpaths.file.display()));
+            if self.props.must_compile_successfully {
+                return
+            } else {
+                self.fatal(&format!("no error pattern specified in {:?}",
+                                    self.testpaths.file.display()));
+            }
         }
         let mut next_err_idx = 0;
         let mut next_err_pat = self.props.error_patterns[next_err_idx].trim();