incr.comp.: Allow for more fine-grained testing of CGU reuse and use it to test incremental ThinLTO.

This commit is contained in:
Michael Woerister 2018-09-18 16:33:24 +02:00
parent b80cb47889
commit ca197323b9
12 changed files with 373 additions and 80 deletions

View File

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

View File

@ -16,6 +16,7 @@
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};

View File

@ -30,16 +30,7 @@
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 @@
ATTR_CLEAN,
ATTR_PARTITION_REUSED,
ATTR_PARTITION_CODEGENED,
ATTR_EXPECTED_CGU_REUSE,
];

View File

@ -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>>>,
@ -1109,6 +1113,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,
@ -1139,6 +1149,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 {

View File

@ -18,6 +18,7 @@
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
}
}

View File

@ -21,6 +21,7 @@
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 @@ pub(crate) fn join(
}
};
sess.cgu_reuse_tracker.check_expected_reuse(sess);
sess.abort_if_errors();
if let Some(time_graph) = self.time_graph {

View File

@ -32,6 +32,7 @@
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);

View File

@ -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 @@ fn check_attr(&self, attr: &ast::Attribute) {
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 @@ fn check_config(&self, attr: &ast::Attribute) -> bool {
debug!("check_config: no match found");
return false;
}
}

View File

@ -930,6 +930,12 @@ pub fn is_builtin_attr(attr: &ast::Attribute) -> bool {
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 \

View File

@ -13,6 +13,7 @@
// revisions:rpass1 rpass2
// aux-build:a.rs
// compile-flags: -Zquery-dep-graph
#![feature(rustc_attrs)]
#![crate_type = "bin"]

View File

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

View File

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