about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/librustc/dep_graph/cgu_reuse_tracker.rs145
-rw-r--r--src/librustc/dep_graph/mod.rs1
-rw-r--r--src/librustc/ich/mod.rs12
-rw-r--r--src/librustc/session/mod.rs11
-rw-r--r--src/librustc_codegen_llvm/back/lto.rs3
-rw-r--r--src/librustc_codegen_llvm/back/write.rs6
-rw-r--r--src/librustc_codegen_llvm/base.rs41
-rw-r--r--src/librustc_incremental/assert_module_sources.rs103
-rw-r--r--src/libsyntax/feature_gate.rs6
-rw-r--r--src/test/incremental/remove-private-item-cross-crate/main.rs1
-rw-r--r--src/test/incremental/thinlto/cgu_invalidated_via_import.rs57
-rw-r--r--src/test/incremental/thinlto/independent_cgus_dont_affect_each_other.rs67
12 files changed, 373 insertions, 80 deletions
diff --git a/src/librustc/dep_graph/cgu_reuse_tracker.rs b/src/librustc/dep_graph/cgu_reuse_tracker.rs
new file mode 100644
index 00000000000..2a0891787f8
--- /dev/null
+++ b/src/librustc/dep_graph/cgu_reuse_tracker.rs
@@ -0,0 +1,145 @@
+// Copyright 2018 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.
+
+//! Some facilities for tracking how codegen-units are reused during incremental
+//! compilition. This is used for incremental compiliation tests and debug
+//! output.
+
+use session::Session;
+use rustc_data_structures::fx::FxHashMap;
+use std::sync::{Arc, Mutex};
+use syntax_pos::Span;
+
+#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
+pub enum CguReuse {
+    No,
+    PreLto,
+    PostLto,
+}
+
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum ComparisonKind {
+    Exact,
+    AtLeast,
+}
+
+struct TrackerData {
+    actual_reuse: FxHashMap<String, CguReuse>,
+    expected_reuse: FxHashMap<String, (String, SendSpan, CguReuse, ComparisonKind)>,
+}
+
+// Span does not implement `Send`, so we can't just store it in the shared
+// `TrackerData` object. Instead of splitting up `TrackerData` into shared and
+// non-shared parts (which would be complicated), we just mark the `Span` here
+// explicitly as `Send`. That's safe because the span data here is only ever
+// accessed from the main thread.
+struct SendSpan(Span);
+unsafe impl Send for SendSpan {}
+
+#[derive(Clone)]
+pub struct CguReuseTracker {
+    data: Option<Arc<Mutex<TrackerData>>>,
+}
+
+impl CguReuseTracker {
+    pub fn new() -> CguReuseTracker {
+        let data = TrackerData {
+            actual_reuse: FxHashMap(),
+            expected_reuse: FxHashMap(),
+        };
+
+        CguReuseTracker {
+            data: Some(Arc::new(Mutex::new(data))),
+        }
+    }
+
+    pub fn new_disabled() -> CguReuseTracker {
+        CguReuseTracker {
+            data: None,
+        }
+    }
+
+    pub fn set_actual_reuse(&self, cgu_name: &str, kind: CguReuse) {
+        if let Some(ref data) = self.data {
+            debug!("set_actual_reuse({:?}, {:?})", cgu_name, kind);
+
+            let prev_reuse = data.lock()
+                                 .unwrap()
+                                 .actual_reuse
+                                 .insert(cgu_name.to_string(), kind);
+
+            if let Some(prev_reuse) = prev_reuse {
+                // The only time it is legal to overwrite reuse state is when
+                // we discover during ThinLTO that we can actually reuse the
+                // post-LTO version of a CGU.
+                assert_eq!(prev_reuse, CguReuse::PreLto);
+            }
+        }
+    }
+
+    pub fn set_expectation(&self,
+                           cgu_name: &str,
+                           cgu_user_name: &str,
+                           error_span: Span,
+                           expected_reuse: CguReuse,
+                           comparison_kind: ComparisonKind) {
+        if let Some(ref data) = self.data {
+            debug!("set_expectation({:?}, {:?}, {:?})", cgu_name,
+                                                        expected_reuse,
+                                                        comparison_kind);
+            let mut data = data.lock().unwrap();
+
+            data.expected_reuse.insert(cgu_name.to_string(),
+                                       (cgu_user_name.to_string(),
+                                        SendSpan(error_span),
+                                        expected_reuse,
+                                        comparison_kind));
+        }
+    }
+
+    pub fn check_expected_reuse(&self, sess: &Session) {
+        if let Some(ref data) = self.data {
+            let data = data.lock().unwrap();
+
+            for (cgu_name, &(ref cgu_user_name,
+                             ref error_span,
+                             expected_reuse,
+                             comparison_kind)) in &data.expected_reuse {
+                if let Some(&actual_reuse) = data.actual_reuse.get(cgu_name) {
+                    let (error, at_least) = match comparison_kind {
+                        ComparisonKind::Exact => {
+                            (expected_reuse != actual_reuse, false)
+                        }
+                        ComparisonKind::AtLeast => {
+                            (actual_reuse < expected_reuse, true)
+                        }
+                    };
+
+                    if error {
+                        let at_least = if at_least { "at least " } else { "" };
+                        let msg = format!("CGU-reuse for `{}` is `{:?}` but \
+                                           should be {}`{:?}`",
+                                          cgu_user_name,
+                                          actual_reuse,
+                                          at_least,
+                                          expected_reuse);
+                        sess.span_err(error_span.0, &msg);
+                    }
+                } else {
+                    let msg = format!("CGU-reuse for `{}` (mangled: `{}`) was \
+                                       not recorded",
+                                       cgu_user_name,
+                                       cgu_name);
+                    sess.span_fatal(error_span.0, &msg);
+                }
+            }
+        }
+    }
+}
diff --git a/src/librustc/dep_graph/mod.rs b/src/librustc/dep_graph/mod.rs
index 8a6f66911ec..158edc6c59e 100644
--- a/src/librustc/dep_graph/mod.rs
+++ b/src/librustc/dep_graph/mod.rs
@@ -16,6 +16,7 @@ mod prev;
 mod query;
 mod safe;
 mod serialized;
+pub mod cgu_reuse_tracker;
 
 pub use self::dep_tracking_map::{DepTrackingMap, DepTrackingMapConfig};
 pub use self::dep_node::{DepNode, DepKind, DepConstructor, WorkProductId, label_strs};
diff --git a/src/librustc/ich/mod.rs b/src/librustc/ich/mod.rs
index 6e5134c3c05..a23bab6226e 100644
--- a/src/librustc/ich/mod.rs
+++ b/src/librustc/ich/mod.rs
@@ -30,16 +30,7 @@ pub const ATTR_IF_THIS_CHANGED: &'static str = "rustc_if_this_changed";
 pub const ATTR_THEN_THIS_WOULD_NEED: &'static str = "rustc_then_this_would_need";
 pub const ATTR_PARTITION_REUSED: &'static str = "rustc_partition_reused";
 pub const ATTR_PARTITION_CODEGENED: &'static str = "rustc_partition_codegened";
-
-
-pub const DEP_GRAPH_ASSERT_ATTRS: &'static [&'static str] = &[
-    ATTR_IF_THIS_CHANGED,
-    ATTR_THEN_THIS_WOULD_NEED,
-    ATTR_DIRTY,
-    ATTR_CLEAN,
-    ATTR_PARTITION_REUSED,
-    ATTR_PARTITION_CODEGENED,
-];
+pub const ATTR_EXPECTED_CGU_REUSE: &'static str = "rustc_expected_cgu_reuse";
 
 pub const IGNORED_ATTRIBUTES: &'static [&'static str] = &[
     "cfg",
@@ -49,4 +40,5 @@ pub const IGNORED_ATTRIBUTES: &'static [&'static str] = &[
     ATTR_CLEAN,
     ATTR_PARTITION_REUSED,
     ATTR_PARTITION_CODEGENED,
+    ATTR_EXPECTED_CGU_REUSE,
 ];
diff --git a/src/librustc/session/mod.rs b/src/librustc/session/mod.rs
index 7bf0d8ecec8..be347253e7f 100644
--- a/src/librustc/session/mod.rs
+++ b/src/librustc/session/mod.rs
@@ -11,6 +11,7 @@
 pub use self::code_stats::{DataTypeKind, SizeKind, FieldInfo, VariantInfo};
 use self::code_stats::CodeStats;
 
+use dep_graph::cgu_reuse_tracker::CguReuseTracker;
 use hir::def_id::CrateNum;
 use rustc_data_structures::fingerprint::Fingerprint;
 
@@ -124,6 +125,9 @@ pub struct Session {
     pub imported_macro_spans: OneThread<RefCell<FxHashMap<Span, (String, Span)>>>,
 
     incr_comp_session: OneThread<RefCell<IncrCompSession>>,
+    /// Used for incremental compilation tests. Will only be populated if
+    /// `-Zquery-dep-graph` is specified.
+    pub cgu_reuse_tracker: CguReuseTracker,
 
     /// Used by -Z profile-queries in util::common
     pub profile_channel: Lock<Option<mpsc::Sender<ProfileQueriesMsg>>>,
@@ -1116,6 +1120,12 @@ pub fn build_session_(
     };
     let working_dir = file_path_mapping.map_prefix(working_dir);
 
+    let cgu_reuse_tracker = if sopts.debugging_opts.query_dep_graph {
+        CguReuseTracker::new()
+    } else {
+        CguReuseTracker::new_disabled()
+    };
+
     let sess = Session {
         target: target_cfg,
         host,
@@ -1146,6 +1156,7 @@ pub fn build_session_(
         injected_panic_runtime: Once::new(),
         imported_macro_spans: OneThread::new(RefCell::new(FxHashMap::default())),
         incr_comp_session: OneThread::new(RefCell::new(IncrCompSession::NotInitialized)),
+        cgu_reuse_tracker,
         self_profiling: Lock::new(SelfProfiler::new()),
         profile_channel: Lock::new(None),
         perf_stats: PerfStats {
diff --git a/src/librustc_codegen_llvm/back/lto.rs b/src/librustc_codegen_llvm/back/lto.rs
index 364b469738f..3ac22f4eaef 100644
--- a/src/librustc_codegen_llvm/back/lto.rs
+++ b/src/librustc_codegen_llvm/back/lto.rs
@@ -18,6 +18,7 @@ use llvm::{True, False};
 use llvm;
 use memmap;
 use rustc::dep_graph::WorkProduct;
+use rustc::dep_graph::cgu_reuse_tracker::CguReuse;
 use rustc::hir::def_id::LOCAL_CRATE;
 use rustc::middle::exported_symbols::SymbolExportLevel;
 use rustc::session::config::{self, Lto};
@@ -538,6 +539,8 @@ fn thin_lto(cgcx: &CodegenContext,
                     let work_product = green_modules[module_name].clone();
                     copy_jobs.push(work_product);
                     info!(" - {}: re-used", module_name);
+                    cgcx.cgu_reuse_tracker.set_actual_reuse(module_name,
+                                                            CguReuse::PostLto);
                     continue
                 }
             }
diff --git a/src/librustc_codegen_llvm/back/write.rs b/src/librustc_codegen_llvm/back/write.rs
index 7b78d4fb4ff..447b505e79c 100644
--- a/src/librustc_codegen_llvm/back/write.rs
+++ b/src/librustc_codegen_llvm/back/write.rs
@@ -21,6 +21,7 @@ use memmap;
 use rustc_incremental::{copy_cgu_workproducts_to_incr_comp_cache_dir,
                         in_incr_comp_dir, in_incr_comp_dir_sess};
 use rustc::dep_graph::{WorkProduct, WorkProductId, WorkProductFileKind};
+use rustc::dep_graph::cgu_reuse_tracker::CguReuseTracker;
 use rustc::middle::cstore::EncodedMetadata;
 use rustc::session::config::{self, OutputFilenames, OutputType, Passes, Sanitizer, Lto};
 use rustc::session::Session;
@@ -377,6 +378,8 @@ pub struct CodegenContext {
     // The incremental compilation session directory, or None if we are not
     // compiling incrementally
     pub incr_comp_session_dir: Option<PathBuf>,
+    // Used to update CGU re-use information during the thinlto phase.
+    pub cgu_reuse_tracker: CguReuseTracker,
     // Channel back to the main control thread to send messages to
     coordinator_send: Sender<Box<dyn Any + Send>>,
     // A reference to the TimeGraph so we can register timings. None means that
@@ -1607,6 +1610,7 @@ fn start_executing_work(tcx: TyCtxt,
         remark: sess.opts.cg.remark.clone(),
         worker: 0,
         incr_comp_session_dir: sess.incr_comp_session_dir_opt().map(|r| r.clone()),
+        cgu_reuse_tracker: sess.cgu_reuse_tracker.clone(),
         coordinator_send,
         diag_emitter: shared_emitter.clone(),
         time_graph,
@@ -2390,6 +2394,8 @@ impl OngoingCodegen {
             }
         };
 
+        sess.cgu_reuse_tracker.check_expected_reuse(sess);
+
         sess.abort_if_errors();
 
         if let Some(time_graph) = self.time_graph {
diff --git a/src/librustc_codegen_llvm/base.rs b/src/librustc_codegen_llvm/base.rs
index c1f6006e684..55dc43ec1fb 100644
--- a/src/librustc_codegen_llvm/base.rs
+++ b/src/librustc_codegen_llvm/base.rs
@@ -32,6 +32,7 @@ use abi;
 use back::write::{self, OngoingCodegen};
 use llvm::{self, TypeKind, get_param};
 use metadata;
+use rustc::dep_graph::cgu_reuse_tracker::CguReuse;
 use rustc::hir::def_id::{CrateNum, DefId, LOCAL_CRATE};
 use rustc::middle::lang_items::StartFnLangItem;
 use rustc::middle::weak_lang_items;
@@ -697,25 +698,18 @@ pub fn iter_globals(llmod: &'ll llvm::Module) -> ValueIter<'ll> {
     }
 }
 
-#[derive(Debug)]
-enum CguReUsable {
-    PreLto,
-    PostLto,
-    No
-}
-
 fn determine_cgu_reuse<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
                                  cgu: &CodegenUnit<'tcx>)
-                                 -> CguReUsable {
+                                 -> CguReuse {
     if !tcx.dep_graph.is_fully_enabled() {
-        return CguReUsable::No
+        return CguReuse::No
     }
 
     let work_product_id = &cgu.work_product_id();
     if tcx.dep_graph.previous_work_product(work_product_id).is_none() {
         // We don't have anything cached for this CGU. This can happen
         // if the CGU did not exist in the previous session.
-        return CguReUsable::No
+        return CguReuse::No
     }
 
     // Try to mark the CGU as green. If it we can do so, it means that nothing
@@ -732,12 +726,12 @@ fn determine_cgu_reuse<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
     if tcx.dep_graph.try_mark_green(tcx, &dep_node).is_some() {
         // We can re-use either the pre- or the post-thinlto state
         if tcx.sess.lto() != Lto::No {
-            CguReUsable::PreLto
+            CguReuse::PreLto
         } else {
-            CguReUsable::PostLto
+            CguReuse::PostLto
         }
     } else {
-        CguReUsable::No
+        CguReuse::No
     }
 }
 
@@ -894,8 +888,11 @@ pub fn codegen_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
         ongoing_codegen.wait_for_signal_to_codegen_item();
         ongoing_codegen.check_for_errors(tcx.sess);
 
-        let loaded_from_cache = match determine_cgu_reuse(tcx, &cgu) {
-            CguReUsable::No => {
+        let cgu_reuse = determine_cgu_reuse(tcx, &cgu);
+        tcx.sess.cgu_reuse_tracker.set_actual_reuse(&cgu.name().as_str(), cgu_reuse);
+
+        match cgu_reuse {
+            CguReuse::No => {
                 let _timing_guard = time_graph.as_ref().map(|time_graph| {
                     time_graph.start(write::CODEGEN_WORKER_TIMELINE,
                                      write::CODEGEN_WORK_PACKAGE_KIND,
@@ -907,14 +904,14 @@ pub fn codegen_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
                 total_codegen_time += start_time.elapsed();
                 false
             }
-            CguReUsable::PreLto => {
+            CguReuse::PreLto => {
                 write::submit_pre_lto_module_to_llvm(tcx, CachedModuleCodegen {
                     name: cgu.name().to_string(),
                     source: cgu.work_product(tcx),
                 });
                 true
             }
-            CguReUsable::PostLto => {
+            CguReuse::PostLto => {
                 write::submit_post_lto_module_to_llvm(tcx, CachedModuleCodegen {
                     name: cgu.name().to_string(),
                     source: cgu.work_product(tcx),
@@ -922,12 +919,6 @@ pub fn codegen_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
                 true
             }
         };
-
-        if tcx.dep_graph.is_fully_enabled() {
-            let dep_node = cgu.codegen_dep_node(tcx);
-            let dep_node_index = tcx.dep_graph.dep_node_index_of(&dep_node);
-            tcx.dep_graph.mark_loaded_from_cache(dep_node_index, loaded_from_cache);
-        }
     }
 
     ongoing_codegen.codegen_finished(tcx);
@@ -938,9 +929,7 @@ pub fn codegen_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
                             "codegen to LLVM IR",
                             total_codegen_time);
 
-    if tcx.sess.opts.incremental.is_some() {
-        ::rustc_incremental::assert_module_sources::assert_module_sources(tcx);
-    }
+    rustc_incremental::assert_module_sources::assert_module_sources(tcx);
 
     symbol_names_test::report_symbol_names(tcx);
 
diff --git a/src/librustc_incremental/assert_module_sources.rs b/src/librustc_incremental/assert_module_sources.rs
index 139159c1639..4ff2529b26d 100644
--- a/src/librustc_incremental/assert_module_sources.rs
+++ b/src/librustc_incremental/assert_module_sources.rs
@@ -26,19 +26,23 @@
 //! The reason that we use `cfg=...` and not `#[cfg_attr]` is so that
 //! the HIR doesn't change as a result of the annotations, which might
 //! perturb the reuse results.
+//!
+//! `#![rustc_expected_cgu_reuse(module="spike", cfg="rpass2", kind="post-lto")]
+//! allows for doing a more fine-grained check to see if pre- or post-lto data
+//! was re-used.
 
 use rustc::hir::def_id::LOCAL_CRATE;
-use rustc::dep_graph::{DepNode, DepConstructor};
+use rustc::dep_graph::cgu_reuse_tracker::*;
 use rustc::mir::mono::CodegenUnitNameBuilder;
 use rustc::ty::TyCtxt;
+use std::collections::BTreeSet;
 use syntax::ast;
-use rustc::ich::{ATTR_PARTITION_REUSED, ATTR_PARTITION_CODEGENED};
+use rustc::ich::{ATTR_PARTITION_REUSED, ATTR_PARTITION_CODEGENED,
+                 ATTR_EXPECTED_CGU_REUSE};
 
 const MODULE: &'static str = "module";
 const CFG: &'static str = "cfg";
-
-#[derive(Debug, PartialEq, Clone, Copy)]
-enum Disposition { Reused, Codegened }
+const KIND: &'static str = "kind";
 
 pub fn assert_module_sources<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>) {
     tcx.dep_graph.with_ignore(|| {
@@ -46,7 +50,18 @@ pub fn assert_module_sources<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>) {
             return;
         }
 
-        let ams = AssertModuleSource { tcx };
+        let available_cgus = tcx
+            .collect_and_partition_mono_items(LOCAL_CRATE)
+            .1
+            .iter()
+            .map(|cgu| format!("{}", cgu.name()))
+            .collect::<BTreeSet<String>>();
+
+        let ams = AssertModuleSource {
+            tcx,
+            available_cgus
+        };
+
         for attr in &tcx.hir.krate().attrs {
             ams.check_attr(attr);
         }
@@ -54,19 +69,39 @@ pub fn assert_module_sources<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>) {
 }
 
 struct AssertModuleSource<'a, 'tcx: 'a> {
-    tcx: TyCtxt<'a, 'tcx, 'tcx>
+    tcx: TyCtxt<'a, 'tcx, 'tcx>,
+    available_cgus: BTreeSet<String>,
 }
 
 impl<'a, 'tcx> AssertModuleSource<'a, 'tcx> {
     fn check_attr(&self, attr: &ast::Attribute) {
-        let disposition = if attr.check_name(ATTR_PARTITION_REUSED) {
-            Disposition::Reused
+        let (expected_reuse, comp_kind) = if attr.check_name(ATTR_PARTITION_REUSED) {
+            (CguReuse::PreLto, ComparisonKind::AtLeast)
         } else if attr.check_name(ATTR_PARTITION_CODEGENED) {
-            Disposition::Codegened
+            (CguReuse::No, ComparisonKind::Exact)
+        } else if attr.check_name(ATTR_EXPECTED_CGU_REUSE) {
+            match &self.field(attr, KIND).as_str()[..] {
+                "no" => (CguReuse::No, ComparisonKind::Exact),
+                "pre-lto" => (CguReuse::PreLto, ComparisonKind::Exact),
+                "post-lto" => (CguReuse::PostLto, ComparisonKind::Exact),
+                "any" => (CguReuse::PreLto, ComparisonKind::AtLeast),
+                other => {
+                    self.tcx.sess.span_fatal(
+                        attr.span,
+                        &format!("unknown cgu-reuse-kind `{}` specified", other));
+                }
+            }
         } else {
             return;
         };
 
+        if !self.tcx.sess.opts.debugging_opts.query_dep_graph {
+            self.tcx.sess.span_fatal(
+                attr.span,
+                &format!("found CGU-reuse attribute but `-Zquery-dep-graph` \
+                          was not specified"));
+        }
+
         if !self.check_config(attr) {
             debug!("check_attr: config does not match, ignoring attr");
             return;
@@ -101,43 +136,24 @@ impl<'a, 'tcx> AssertModuleSource<'a, 'tcx> {
 
         debug!("mapping '{}' to cgu name '{}'", self.field(attr, MODULE), cgu_name);
 
-        let dep_node = DepNode::new(self.tcx,
-                                    DepConstructor::CompileCodegenUnit(cgu_name));
-
-        if let Some(loaded_from_cache) = self.tcx.dep_graph.was_loaded_from_cache(&dep_node) {
-            match (disposition, loaded_from_cache) {
-                (Disposition::Reused, false) => {
-                    self.tcx.sess.span_err(
-                        attr.span,
-                        &format!("expected module named `{}` to be Reused but is Codegened",
-                                 user_path));
-                }
-                (Disposition::Codegened, true) => {
-                    self.tcx.sess.span_err(
-                        attr.span,
-                        &format!("expected module named `{}` to be Codegened but is Reused",
-                                 user_path));
-                }
-                (Disposition::Reused, true) |
-                (Disposition::Codegened, false) => {
-                    // These are what we would expect.
-                }
-            }
-        } else {
-            let available_cgus = self.tcx
-                .collect_and_partition_mono_items(LOCAL_CRATE)
-                .1
-                .iter()
-                .map(|cgu| format!("{}", cgu.name()))
-                .collect::<Vec<String>>()
-                .join(", ");
-
+        if !self.available_cgus.contains(&cgu_name.as_str()[..]) {
             self.tcx.sess.span_err(attr.span,
-                &format!("no module named `{}` (mangled: {}).\nAvailable modules: {}",
+                &format!("no module named `{}` (mangled: {}). \
+                          Available modules: {}",
                     user_path,
                     cgu_name,
-                    available_cgus));
+                    self.available_cgus
+                        .iter()
+                        .cloned()
+                        .collect::<Vec<_>>()
+                        .join(", ")));
         }
+
+        self.tcx.sess.cgu_reuse_tracker.set_expectation(&cgu_name.as_str(),
+                                                        &user_path,
+                                                        attr.span,
+                                                        expected_reuse,
+                                                        comp_kind);
     }
 
     fn field(&self, attr: &ast::Attribute, name: &str) -> ast::Name {
@@ -171,5 +187,4 @@ impl<'a, 'tcx> AssertModuleSource<'a, 'tcx> {
         debug!("check_config: no match found");
         return false;
     }
-
 }
diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs
index d44d2146d82..65ffd96e7a0 100644
--- a/src/libsyntax/feature_gate.rs
+++ b/src/libsyntax/feature_gate.rs
@@ -933,6 +933,12 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG
                                                        is just used for rustc unit tests \
                                                        and will never be stable",
                                                       cfg_fn!(rustc_attrs))),
+    ("rustc_expected_cgu_reuse", Whitelisted, Gated(Stability::Unstable,
+                                                    "rustc_attrs",
+                                                    "this attribute \
+                                                     is just used for rustc unit tests \
+                                                     and will never be stable",
+                                                    cfg_fn!(rustc_attrs))),
     ("rustc_synthetic", Whitelisted, Gated(Stability::Unstable,
                                                       "rustc_attrs",
                                                       "this attribute \
diff --git a/src/test/incremental/remove-private-item-cross-crate/main.rs b/src/test/incremental/remove-private-item-cross-crate/main.rs
index d94cb403da8..68806561547 100644
--- a/src/test/incremental/remove-private-item-cross-crate/main.rs
+++ b/src/test/incremental/remove-private-item-cross-crate/main.rs
@@ -13,6 +13,7 @@
 
 // revisions:rpass1 rpass2
 // aux-build:a.rs
+// compile-flags: -Zquery-dep-graph
 
 #![feature(rustc_attrs)]
 #![crate_type = "bin"]
diff --git a/src/test/incremental/thinlto/cgu_invalidated_via_import.rs b/src/test/incremental/thinlto/cgu_invalidated_via_import.rs
new file mode 100644
index 00000000000..c9e1ab89e50
--- /dev/null
+++ b/src/test/incremental/thinlto/cgu_invalidated_via_import.rs
@@ -0,0 +1,57 @@
+// Copyright 2018 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 checks that the LTO phase is re-done for CGUs that import something
+// via ThinLTO and that imported thing changes while the definition of the CGU
+// stays untouched.
+
+// revisions: cfail1 cfail2 cfail3
+// compile-flags: -Z query-dep-graph -O
+// compile-pass
+
+#![feature(rustc_attrs)]
+#![crate_type="rlib"]
+
+#![rustc_expected_cgu_reuse(module="cgu_invalidated_via_import-foo",
+                            cfg="cfail2",
+                            kind="no")]
+#![rustc_expected_cgu_reuse(module="cgu_invalidated_via_import-foo",
+                            cfg="cfail3",
+                            kind="post-lto")]
+
+#![rustc_expected_cgu_reuse(module="cgu_invalidated_via_import-bar",
+                            cfg="cfail2",
+                            kind="pre-lto")]
+#![rustc_expected_cgu_reuse(module="cgu_invalidated_via_import-bar",
+                            cfg="cfail3",
+                            kind="post-lto")]
+
+mod foo {
+
+    // Trivial functions like this one are imported very reliably by ThinLTO.
+    #[cfg(cfail1)]
+    pub fn inlined_fn() -> u32 {
+        1234
+    }
+
+    #[cfg(not(cfail1))]
+    pub fn inlined_fn() -> u32 {
+        1234
+    }
+}
+
+pub mod bar {
+    use foo::inlined_fn;
+
+    pub fn caller() -> u32 {
+        inlined_fn()
+    }
+}
diff --git a/src/test/incremental/thinlto/independent_cgus_dont_affect_each_other.rs b/src/test/incremental/thinlto/independent_cgus_dont_affect_each_other.rs
new file mode 100644
index 00000000000..d0d6d6fdc24
--- /dev/null
+++ b/src/test/incremental/thinlto/independent_cgus_dont_affect_each_other.rs
@@ -0,0 +1,67 @@
+// Copyright 2018 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 checks that a change in a CGU does not invalidate an unrelated CGU
+// during incremental ThinLTO.
+
+// revisions: cfail1 cfail2 cfail3
+// compile-flags: -Z query-dep-graph -O
+// compile-pass
+
+#![feature(rustc_attrs)]
+#![crate_type="rlib"]
+
+#![rustc_expected_cgu_reuse(module="independent_cgus_dont_affect_each_other-foo",
+                            cfg="cfail2",
+                            kind="no")]
+#![rustc_expected_cgu_reuse(module="independent_cgus_dont_affect_each_other-foo",
+                            cfg="cfail3",
+                            kind="post-lto")]
+
+#![rustc_expected_cgu_reuse(module="independent_cgus_dont_affect_each_other-bar",
+                            cfg="cfail2",
+                            kind="pre-lto")]
+#![rustc_expected_cgu_reuse(module="independent_cgus_dont_affect_each_other-bar",
+                            cfg="cfail3",
+                            kind="post-lto")]
+
+#![rustc_expected_cgu_reuse(module="independent_cgus_dont_affect_each_other-baz",
+                            cfg="cfail2",
+                            kind="post-lto")]
+#![rustc_expected_cgu_reuse(module="independent_cgus_dont_affect_each_other-baz",
+                            cfg="cfail3",
+                            kind="post-lto")]
+mod foo {
+
+    #[cfg(cfail1)]
+    pub fn inlined_fn() -> u32 {
+        1234
+    }
+
+    #[cfg(not(cfail1))]
+    pub fn inlined_fn() -> u32 {
+        1234
+    }
+}
+
+pub mod bar {
+    use foo::inlined_fn;
+
+    pub fn caller() -> u32 {
+        inlined_fn()
+    }
+}
+
+pub mod baz {
+    pub fn unrelated_to_other_fns() -> u64 {
+        0xbeef
+    }
+}