1 //! Code to save/load the dep-graph from files.
2
3 use crate::errors;
4 use rustc_data_structures::memmap::Mmap;
5 use rustc_data_structures::unord::UnordMap;
6 use rustc_middle::dep_graph::{SerializedDepGraph, WorkProduct, WorkProductId};
7 use rustc_middle::query::on_disk_cache::OnDiskCache;
8 use rustc_serialize::opaque::MemDecoder;
9 use rustc_serialize::Decodable;
10 use rustc_session::config::IncrementalStateAssertion;
11 use rustc_session::Session;
12 use std::path::{Path, PathBuf};
13
14 use super::data::*;
15 use super::file_format;
16 use super::fs::*;
17 use super::work_product;
18
19 type WorkProductMap = UnordMap<WorkProductId, WorkProduct>;
20
21 #[derive(Debug)]
22 /// Represents the result of an attempt to load incremental compilation data.
23 pub enum LoadResult<T> {
24 /// Loading was successful.
25 Ok {
26 #[allow(missing_docs)]
27 data: T,
28 },
29 /// The file either didn't exist or was produced by an incompatible compiler version.
30 DataOutOfDate,
31 /// Loading the dep graph failed.
32 LoadDepGraph(PathBuf, std::io::Error),
33 /// Decoding loaded incremental cache failed.
34 DecodeIncrCache(Box<dyn std::any::Any + Send>),
35 }
36
37 impl<T: Default> LoadResult<T> {
38 /// Accesses the data returned in [`LoadResult::Ok`].
open(self, sess: &Session) -> T39 pub fn open(self, sess: &Session) -> T {
40 // Check for errors when using `-Zassert-incremental-state`
41 match (sess.opts.assert_incr_state, &self) {
42 (Some(IncrementalStateAssertion::NotLoaded), LoadResult::Ok { .. }) => {
43 sess.emit_fatal(errors::AssertNotLoaded);
44 }
45 (
46 Some(IncrementalStateAssertion::Loaded),
47 LoadResult::LoadDepGraph(..)
48 | LoadResult::DecodeIncrCache(..)
49 | LoadResult::DataOutOfDate,
50 ) => {
51 sess.emit_fatal(errors::AssertLoaded);
52 }
53 _ => {}
54 };
55
56 match self {
57 LoadResult::LoadDepGraph(path, err) => {
58 sess.emit_warning(errors::LoadDepGraph { path, err });
59 Default::default()
60 }
61 LoadResult::DecodeIncrCache(err) => {
62 sess.emit_warning(errors::DecodeIncrCache { err: format!("{err:?}") });
63 Default::default()
64 }
65 LoadResult::DataOutOfDate => {
66 if let Err(err) = delete_all_session_dir_contents(sess) {
67 sess.emit_err(errors::DeleteIncompatible { path: dep_graph_path(sess), err });
68 }
69 Default::default()
70 }
71 LoadResult::Ok { data } => data,
72 }
73 }
74 }
75
load_data(path: &Path, sess: &Session) -> LoadResult<(Mmap, usize)>76 fn load_data(path: &Path, sess: &Session) -> LoadResult<(Mmap, usize)> {
77 load_data_no_sess(
78 path,
79 sess.opts.unstable_opts.incremental_info,
80 sess.is_nightly_build(),
81 sess.cfg_version,
82 )
83 }
84
load_data_no_sess( path: &Path, report_incremental_info: bool, is_nightly_build: bool, cfg_version: &'static str, ) -> LoadResult<(Mmap, usize)>85 fn load_data_no_sess(
86 path: &Path,
87 report_incremental_info: bool,
88 is_nightly_build: bool,
89 cfg_version: &'static str,
90 ) -> LoadResult<(Mmap, usize)> {
91 match file_format::read_file(path, report_incremental_info, is_nightly_build, cfg_version) {
92 Ok(Some(data_and_pos)) => LoadResult::Ok { data: data_and_pos },
93 Ok(None) => {
94 // The file either didn't exist or was produced by an incompatible
95 // compiler version. Neither is an error.
96 LoadResult::DataOutOfDate
97 }
98 Err(err) => LoadResult::LoadDepGraph(path.to_path_buf(), err),
99 }
100 }
101
delete_dirty_work_product(sess: &Session, swp: SerializedWorkProduct)102 fn delete_dirty_work_product(sess: &Session, swp: SerializedWorkProduct) {
103 debug!("delete_dirty_work_product({:?})", swp);
104 work_product::delete_workproduct_files(sess, &swp.work_product);
105 }
106
107 /// Either a result that has already be computed or a
108 /// handle that will let us wait until it is computed
109 /// by a background thread.
110 pub enum MaybeAsync<T> {
111 Sync(T),
112 Async(std::thread::JoinHandle<T>),
113 }
114
115 impl<T> MaybeAsync<LoadResult<T>> {
116 /// Accesses the data returned in [`LoadResult::Ok`] in an asynchronous way if possible.
open(self) -> LoadResult<T>117 pub fn open(self) -> LoadResult<T> {
118 match self {
119 MaybeAsync::Sync(result) => result,
120 MaybeAsync::Async(handle) => {
121 handle.join().unwrap_or_else(|e| LoadResult::DecodeIncrCache(e))
122 }
123 }
124 }
125 }
126
127 /// An asynchronous type for computing the dependency graph.
128 pub type DepGraphFuture = MaybeAsync<LoadResult<(SerializedDepGraph, WorkProductMap)>>;
129
130 /// Launch a thread and load the dependency graph in the background.
load_dep_graph(sess: &Session) -> DepGraphFuture131 pub fn load_dep_graph(sess: &Session) -> DepGraphFuture {
132 // Since `sess` isn't `Sync`, we perform all accesses to `sess`
133 // before we fire the background thread.
134
135 let prof = sess.prof.clone();
136
137 if sess.opts.incremental.is_none() {
138 // No incremental compilation.
139 return MaybeAsync::Sync(LoadResult::Ok { data: Default::default() });
140 }
141
142 let _timer = sess.prof.generic_activity("incr_comp_prepare_load_dep_graph");
143
144 // Calling `sess.incr_comp_session_dir()` will panic if `sess.opts.incremental.is_none()`.
145 // Fortunately, we just checked that this isn't the case.
146 let path = dep_graph_path(&sess);
147 let report_incremental_info = sess.opts.unstable_opts.incremental_info;
148 let expected_hash = sess.opts.dep_tracking_hash(false);
149
150 let mut prev_work_products = UnordMap::default();
151
152 // If we are only building with -Zquery-dep-graph but without an actual
153 // incr. comp. session directory, we skip this. Otherwise we'd fail
154 // when trying to load work products.
155 if sess.incr_comp_session_dir_opt().is_some() {
156 let work_products_path = work_products_path(sess);
157 let load_result = load_data(&work_products_path, sess);
158
159 if let LoadResult::Ok { data: (work_products_data, start_pos) } = load_result {
160 // Decode the list of work_products
161 let mut work_product_decoder = MemDecoder::new(&work_products_data[..], start_pos);
162 let work_products: Vec<SerializedWorkProduct> =
163 Decodable::decode(&mut work_product_decoder);
164
165 for swp in work_products {
166 let all_files_exist = swp.work_product.saved_files.items().all(|(_, path)| {
167 let exists = in_incr_comp_dir_sess(sess, path).exists();
168 if !exists && sess.opts.unstable_opts.incremental_info {
169 eprintln!("incremental: could not find file for work product: {path}",);
170 }
171 exists
172 });
173
174 if all_files_exist {
175 debug!("reconcile_work_products: all files for {:?} exist", swp);
176 prev_work_products.insert(swp.id, swp.work_product);
177 } else {
178 debug!("reconcile_work_products: some file for {:?} does not exist", swp);
179 delete_dirty_work_product(sess, swp);
180 }
181 }
182 }
183 }
184
185 let is_nightly_build = sess.is_nightly_build();
186 let cfg_version = sess.cfg_version;
187
188 MaybeAsync::Async(std::thread::spawn(move || {
189 let _prof_timer = prof.generic_activity("incr_comp_load_dep_graph");
190
191 match load_data_no_sess(&path, report_incremental_info, is_nightly_build, cfg_version) {
192 LoadResult::DataOutOfDate => LoadResult::DataOutOfDate,
193 LoadResult::LoadDepGraph(path, err) => LoadResult::LoadDepGraph(path, err),
194 LoadResult::DecodeIncrCache(err) => LoadResult::DecodeIncrCache(err),
195 LoadResult::Ok { data: (bytes, start_pos) } => {
196 let mut decoder = MemDecoder::new(&bytes, start_pos);
197 let prev_commandline_args_hash = u64::decode(&mut decoder);
198
199 if prev_commandline_args_hash != expected_hash {
200 if report_incremental_info {
201 eprintln!(
202 "[incremental] completely ignoring cache because of \
203 differing commandline arguments"
204 );
205 }
206 // We can't reuse the cache, purge it.
207 debug!("load_dep_graph_new: differing commandline arg hashes");
208
209 // No need to do any further work
210 return LoadResult::DataOutOfDate;
211 }
212
213 let dep_graph = SerializedDepGraph::decode(&mut decoder);
214
215 LoadResult::Ok { data: (dep_graph, prev_work_products) }
216 }
217 }
218 }))
219 }
220
221 /// Attempts to load the query result cache from disk
222 ///
223 /// If we are not in incremental compilation mode, returns `None`.
224 /// Otherwise, tries to load the query result cache from disk,
225 /// creating an empty cache if it could not be loaded.
load_query_result_cache(sess: &Session) -> Option<OnDiskCache<'_>>226 pub fn load_query_result_cache(sess: &Session) -> Option<OnDiskCache<'_>> {
227 if sess.opts.incremental.is_none() {
228 return None;
229 }
230
231 let _prof_timer = sess.prof.generic_activity("incr_comp_load_query_result_cache");
232
233 match load_data(&query_cache_path(sess), sess) {
234 LoadResult::Ok { data: (bytes, start_pos) } => {
235 Some(OnDiskCache::new(sess, bytes, start_pos))
236 }
237 _ => Some(OnDiskCache::new_empty(sess.source_map())),
238 }
239 }
240