Skip to content

Commit 54c0dcf

Browse files
authored
Auto merge of #34956 - nikomatsakis:incr-comp-o-files, r=mw
Enable reuse of `.o` files if nothing has changed This PR completes a first "spike" for incremental compilation by enabling us to reuse `.o` files when nothing has changed. When in incr. mode, we will save `.o` files into the temporary directory, then copy them back out again if they are still valid. The code is still a bit rough but it does seem to work. =) r? @michaelwoerister Fixes #34036 Fixes #34037 Fixes #34038
2 parents d1df3fe + 42cd5d4 commit 54c0dcf

File tree

36 files changed

+1245
-197
lines changed

36 files changed

+1245
-197
lines changed

src/librustc/dep_graph/dep_node.rs

+19
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
// except according to those terms.
1010

1111
use std::fmt::Debug;
12+
use std::sync::Arc;
1213

1314
macro_rules! try_opt {
1415
($e:expr) => (
@@ -45,6 +46,10 @@ pub enum DepNode<D: Clone + Debug> {
4546
// in an extern crate.
4647
MetaData(D),
4748

49+
// Represents some artifact that we save to disk. Note that these
50+
// do not have a def-id as part of their identifier.
51+
WorkProduct(Arc<WorkProductId>),
52+
4853
// Represents different phases in the compiler.
4954
CrateReader,
5055
CollectLanguageItems,
@@ -189,6 +194,11 @@ impl<D: Clone + Debug> DepNode<D> {
189194
TransCrate => Some(TransCrate),
190195
TransWriteMetadata => Some(TransWriteMetadata),
191196
LinkBinary => Some(LinkBinary),
197+
198+
// work product names do not need to be mapped, because
199+
// they are always absolute.
200+
WorkProduct(ref id) => Some(WorkProduct(id.clone())),
201+
192202
Hir(ref d) => op(d).map(Hir),
193203
MetaData(ref d) => op(d).map(MetaData),
194204
CollectItem(ref d) => op(d).map(CollectItem),
@@ -229,3 +239,12 @@ impl<D: Clone + Debug> DepNode<D> {
229239
}
230240
}
231241
}
242+
243+
/// A "work product" corresponds to a `.o` (or other) file that we
244+
/// save in between runs. These ids do not have a DefId but rather
245+
/// some independent path or string that persists between runs without
246+
/// the need to be mapped or unmapped. (This ensures we can serialize
247+
/// them even in the absence of a tcx.)
248+
#[derive(Clone, Debug, PartialEq, Eq, Hash, RustcEncodable, RustcDecodable)]
249+
pub struct WorkProductId(pub String);
250+

src/librustc/dep_graph/graph.rs

+106-9
Original file line numberDiff line numberDiff line change
@@ -9,42 +9,65 @@
99
// except according to those terms.
1010

1111
use hir::def_id::DefId;
12+
use rustc_data_structures::fnv::FnvHashMap;
13+
use session::config::OutputType;
14+
use std::cell::{Ref, RefCell};
1215
use std::rc::Rc;
16+
use std::sync::Arc;
1317

14-
use super::dep_node::DepNode;
18+
use super::dep_node::{DepNode, WorkProductId};
1519
use super::query::DepGraphQuery;
1620
use super::raii;
1721
use super::thread::{DepGraphThreadData, DepMessage};
1822

1923
#[derive(Clone)]
2024
pub struct DepGraph {
21-
data: Rc<DepGraphThreadData>
25+
data: Rc<DepGraphData>
26+
}
27+
28+
struct DepGraphData {
29+
/// We send messages to the thread to let it build up the dep-graph
30+
/// from the current run.
31+
thread: DepGraphThreadData,
32+
33+
/// When we load, there may be `.o` files, cached mir, or other such
34+
/// things available to us. If we find that they are not dirty, we
35+
/// load the path to the file storing those work-products here into
36+
/// this map. We can later look for and extract that data.
37+
previous_work_products: RefCell<FnvHashMap<Arc<WorkProductId>, WorkProduct>>,
38+
39+
/// Work-products that we generate in this run.
40+
work_products: RefCell<FnvHashMap<Arc<WorkProductId>, WorkProduct>>,
2241
}
2342

2443
impl DepGraph {
2544
pub fn new(enabled: bool) -> DepGraph {
2645
DepGraph {
27-
data: Rc::new(DepGraphThreadData::new(enabled))
46+
data: Rc::new(DepGraphData {
47+
thread: DepGraphThreadData::new(enabled),
48+
previous_work_products: RefCell::new(FnvHashMap()),
49+
work_products: RefCell::new(FnvHashMap())
50+
})
2851
}
2952
}
3053

3154
/// True if we are actually building a dep-graph. If this returns false,
3255
/// then the other methods on this `DepGraph` will have no net effect.
3356
#[inline]
3457
pub fn enabled(&self) -> bool {
35-
self.data.enabled()
58+
self.data.thread.enabled()
3659
}
3760

3861
pub fn query(&self) -> DepGraphQuery<DefId> {
39-
self.data.query()
62+
self.data.thread.query()
4063
}
4164

4265
pub fn in_ignore<'graph>(&'graph self) -> raii::IgnoreTask<'graph> {
43-
raii::IgnoreTask::new(&self.data)
66+
raii::IgnoreTask::new(&self.data.thread)
4467
}
4568

4669
pub fn in_task<'graph>(&'graph self, key: DepNode<DefId>) -> raii::DepTask<'graph> {
47-
raii::DepTask::new(&self.data, key)
70+
raii::DepTask::new(&self.data.thread, key)
4871
}
4972

5073
pub fn with_ignore<OP,R>(&self, op: OP) -> R
@@ -62,10 +85,84 @@ impl DepGraph {
6285
}
6386

6487
pub fn read(&self, v: DepNode<DefId>) {
65-
self.data.enqueue(DepMessage::Read(v));
88+
self.data.thread.enqueue(DepMessage::Read(v));
6689
}
6790

6891
pub fn write(&self, v: DepNode<DefId>) {
69-
self.data.enqueue(DepMessage::Write(v));
92+
self.data.thread.enqueue(DepMessage::Write(v));
93+
}
94+
95+
/// Indicates that a previous work product exists for `v`. This is
96+
/// invoked during initial start-up based on what nodes are clean
97+
/// (and what files exist in the incr. directory).
98+
pub fn insert_previous_work_product(&self, v: &Arc<WorkProductId>, data: WorkProduct) {
99+
debug!("insert_previous_work_product({:?}, {:?})", v, data);
100+
self.data.previous_work_products.borrow_mut()
101+
.insert(v.clone(), data);
102+
}
103+
104+
/// Indicates that we created the given work-product in this run
105+
/// for `v`. This record will be preserved and loaded in the next
106+
/// run.
107+
pub fn insert_work_product(&self, v: &Arc<WorkProductId>, data: WorkProduct) {
108+
debug!("insert_work_product({:?}, {:?})", v, data);
109+
self.data.work_products.borrow_mut()
110+
.insert(v.clone(), data);
70111
}
112+
113+
/// Check whether a previous work product exists for `v` and, if
114+
/// so, return the path that leads to it. Used to skip doing work.
115+
pub fn previous_work_product(&self, v: &Arc<WorkProductId>) -> Option<WorkProduct> {
116+
self.data.previous_work_products.borrow()
117+
.get(v)
118+
.cloned()
119+
}
120+
121+
/// Access the map of work-products created during this run. Only
122+
/// used during saving of the dep-graph.
123+
pub fn work_products(&self) -> Ref<FnvHashMap<Arc<WorkProductId>, WorkProduct>> {
124+
self.data.work_products.borrow()
125+
}
126+
}
127+
128+
/// A "work product" is an intermediate result that we save into the
129+
/// incremental directory for later re-use. The primary example are
130+
/// the object files that we save for each partition at code
131+
/// generation time.
132+
///
133+
/// Each work product is associated with a dep-node, representing the
134+
/// process that produced the work-product. If that dep-node is found
135+
/// to be dirty when we load up, then we will delete the work-product
136+
/// at load time. If the work-product is found to be clean, then we
137+
/// will keep a record in the `previous_work_products` list.
138+
///
139+
/// In addition, work products have an associated hash. This hash is
140+
/// an extra hash that can be used to decide if the work-product from
141+
/// a previous compilation can be re-used (in addition to the dirty
142+
/// edges check).
143+
///
144+
/// As the primary example, consider the object files we generate for
145+
/// each partition. In the first run, we create partitions based on
146+
/// the symbols that need to be compiled. For each partition P, we
147+
/// hash the symbols in P and create a `WorkProduct` record associated
148+
/// with `DepNode::TransPartition(P)`; the hash is the set of symbols
149+
/// in P.
150+
///
151+
/// The next time we compile, if the `DepNode::TransPartition(P)` is
152+
/// judged to be clean (which means none of the things we read to
153+
/// generate the partition were found to be dirty), it will be loaded
154+
/// into previous work products. We will then regenerate the set of
155+
/// symbols in the partition P and hash them (note that new symbols
156+
/// may be added -- for example, new monomorphizations -- even if
157+
/// nothing in P changed!). We will compare that hash against the
158+
/// previous hash. If it matches up, we can reuse the object file.
159+
#[derive(Clone, Debug, RustcEncodable, RustcDecodable)]
160+
pub struct WorkProduct {
161+
/// Extra hash used to decide if work-product is still suitable;
162+
/// note that this is *not* a hash of the work-product itself.
163+
/// See documentation on `WorkProduct` type for an example.
164+
pub input_hash: u64,
165+
166+
/// Saved files associated with this CGU
167+
pub saved_files: Vec<(OutputType, String)>,
71168
}

src/librustc/dep_graph/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ mod visit;
2020

2121
pub use self::dep_tracking_map::{DepTrackingMap, DepTrackingMapConfig};
2222
pub use self::dep_node::DepNode;
23+
pub use self::dep_node::WorkProductId;
2324
pub use self::graph::DepGraph;
25+
pub use self::graph::WorkProduct;
2426
pub use self::query::DepGraphQuery;
2527
pub use self::visit::visit_all_items_in_krate;
2628
pub use self::raii::DepTask;

src/librustc/session/config.rs

+13-10
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ pub enum DebugInfoLevel {
6161
FullDebugInfo,
6262
}
6363

64-
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
64+
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, RustcEncodable, RustcDecodable)]
6565
pub enum OutputType {
6666
Bitcode,
6767
Assembly,
@@ -105,6 +105,17 @@ impl OutputType {
105105
OutputType::DepInfo => "dep-info",
106106
}
107107
}
108+
109+
pub fn extension(&self) -> &'static str {
110+
match *self {
111+
OutputType::Bitcode => "bc",
112+
OutputType::Assembly => "s",
113+
OutputType::LlvmAssembly => "ll",
114+
OutputType::Object => "o",
115+
OutputType::DepInfo => "d",
116+
OutputType::Exe => "",
117+
}
118+
}
108119
}
109120

110121
#[derive(Clone)]
@@ -215,15 +226,7 @@ impl OutputFilenames {
215226
flavor: OutputType,
216227
codegen_unit_name: Option<&str>)
217228
-> PathBuf {
218-
let extension = match flavor {
219-
OutputType::Bitcode => "bc",
220-
OutputType::Assembly => "s",
221-
OutputType::LlvmAssembly => "ll",
222-
OutputType::Object => "o",
223-
OutputType::DepInfo => "d",
224-
OutputType::Exe => "",
225-
};
226-
229+
let extension = flavor.extension();
227230
self.temp_path_ext(extension, codegen_unit_name)
228231
}
229232

src/librustc/session/mod.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ pub struct Session {
8080
// forms a unique global identifier for the crate. It is used to allow
8181
// multiple crates with the same name to coexist. See the
8282
// trans::back::symbol_names module for more information.
83-
pub crate_disambiguator: Cell<ast::Name>,
83+
pub crate_disambiguator: RefCell<token::InternedString>,
8484
pub features: RefCell<feature_gate::Features>,
8585

8686
/// The maximum recursion limit for potentially infinitely recursive
@@ -106,6 +106,9 @@ pub struct Session {
106106
}
107107

108108
impl Session {
109+
pub fn local_crate_disambiguator(&self) -> token::InternedString {
110+
self.crate_disambiguator.borrow().clone()
111+
}
109112
pub fn struct_span_warn<'a, S: Into<MultiSpan>>(&'a self,
110113
sp: S,
111114
msg: &str)
@@ -438,7 +441,7 @@ pub fn build_session_(sopts: config::Options,
438441
plugin_attributes: RefCell::new(Vec::new()),
439442
crate_types: RefCell::new(Vec::new()),
440443
dependency_formats: RefCell::new(FnvHashMap()),
441-
crate_disambiguator: Cell::new(token::intern("")),
444+
crate_disambiguator: RefCell::new(token::intern("").as_str()),
442445
features: RefCell::new(feature_gate::Features::new()),
443446
recursion_limit: Cell::new(64),
444447
next_node_id: Cell::new(1),

src/librustc/ty/context.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,7 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
504504

505505
pub fn crate_disambiguator(self, cnum: ast::CrateNum) -> token::InternedString {
506506
if cnum == LOCAL_CRATE {
507-
self.sess.crate_disambiguator.get().as_str()
507+
self.sess.local_crate_disambiguator()
508508
} else {
509509
self.sess.cstore.crate_disambiguator(cnum)
510510
}

src/librustc/util/fs.rs

+14
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
use std::path::{self, Path, PathBuf};
1212
use std::ffi::OsString;
13+
use std::fs;
14+
use std::io;
1315

1416
// Unfortunately, on windows, it looks like msvcrt.dll is silently translating
1517
// verbatim paths under the hood to non-verbatim paths! This manifests itself as
@@ -53,3 +55,15 @@ pub fn fix_windows_verbatim_for_gcc(p: &Path) -> PathBuf {
5355
_ => p.to_path_buf(),
5456
}
5557
}
58+
59+
/// Copy `p` into `q`, preferring to use hard-linking if possible. If
60+
/// `q` already exists, it is removed first.
61+
pub fn link_or_copy<P: AsRef<Path>, Q: AsRef<Path>>(p: P, q: Q) -> io::Result<()> {
62+
let p = p.as_ref();
63+
let q = q.as_ref();
64+
if q.exists() {
65+
try!(fs::remove_file(&q));
66+
}
67+
fs::hard_link(p, q)
68+
.or_else(|_| fs::copy(p, q).map(|_| ()))
69+
}

0 commit comments

Comments
 (0)