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