rustc_incremental/persist/
load.rs

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