• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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