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(
95    sess: &Session,
96    deps: &DepsType,
97) -> LoadResult<(Arc<SerializedDepGraph>, WorkProductMap)> {
98    let prof = sess.prof.clone();
99
100    if sess.opts.incremental.is_none() {
101        // No incremental compilation.
102        return LoadResult::Ok { data: Default::default() };
103    }
104
105    let _timer = sess.prof.generic_activity("incr_comp_prepare_load_dep_graph");
106
107    // Calling `sess.incr_comp_session_dir()` will panic if `sess.opts.incremental.is_none()`.
108    // Fortunately, we just checked that this isn't the case.
109    let path = dep_graph_path(sess);
110    let expected_hash = sess.opts.dep_tracking_hash(false);
111
112    let mut prev_work_products = UnordMap::default();
113
114    // If we are only building with -Zquery-dep-graph but without an actual
115    // incr. comp. session directory, we skip this. Otherwise we'd fail
116    // when trying to load work products.
117    if sess.incr_comp_session_dir_opt().is_some() {
118        let work_products_path = work_products_path(sess);
119        let load_result = load_data(&work_products_path, sess);
120
121        if let LoadResult::Ok { data: (work_products_data, start_pos) } = load_result {
122            // Decode the list of work_products
123            let Ok(mut work_product_decoder) = MemDecoder::new(&work_products_data[..], start_pos)
124            else {
125                sess.dcx().emit_warn(errors::CorruptFile { path: &work_products_path });
126                return LoadResult::DataOutOfDate;
127            };
128            let work_products: Vec<SerializedWorkProduct> =
129                Decodable::decode(&mut work_product_decoder);
130
131            for swp in work_products {
132                let all_files_exist = swp.work_product.saved_files.items().all(|(_, path)| {
133                    let exists = in_incr_comp_dir_sess(sess, path).exists();
134                    if !exists && sess.opts.unstable_opts.incremental_info {
135                        eprintln!("incremental: could not find file for work product: {path}",);
136                    }
137                    exists
138                });
139
140                if all_files_exist {
141                    debug!("reconcile_work_products: all files for {:?} exist", swp);
142                    prev_work_products.insert(swp.id, swp.work_product);
143                } else {
144                    debug!("reconcile_work_products: some file for {:?} does not exist", swp);
145                    delete_dirty_work_product(sess, swp);
146                }
147            }
148        }
149    }
150
151    let _prof_timer = prof.generic_activity("incr_comp_load_dep_graph");
152
153    match load_data(&path, sess) {
154        LoadResult::DataOutOfDate => LoadResult::DataOutOfDate,
155        LoadResult::LoadDepGraph(path, err) => LoadResult::LoadDepGraph(path, err),
156        LoadResult::Ok { data: (bytes, start_pos) } => {
157            let Ok(mut decoder) = MemDecoder::new(&bytes, start_pos) else {
158                sess.dcx().emit_warn(errors::CorruptFile { path: &path });
159                return LoadResult::DataOutOfDate;
160            };
161            let prev_commandline_args_hash = Hash64::decode(&mut decoder);
162
163            if prev_commandline_args_hash != expected_hash {
164                if sess.opts.unstable_opts.incremental_info {
165                    eprintln!(
166                        "[incremental] completely ignoring cache because of \
167                                    differing commandline arguments"
168                    );
169                }
170                // We can't reuse the cache, purge it.
171                debug!("load_dep_graph_new: differing commandline arg hashes");
172
173                // No need to do any further work
174                return LoadResult::DataOutOfDate;
175            }
176
177            let dep_graph = SerializedDepGraph::decode::<DepsType>(&mut decoder, deps);
178
179            LoadResult::Ok { data: (dep_graph, prev_work_products) }
180        }
181    }
182}
183
184/// Attempts to load the query result cache from disk
185///
186/// If we are not in incremental compilation mode, returns `None`.
187/// Otherwise, tries to load the query result cache from disk,
188/// creating an empty cache if it could not be loaded.
189pub fn load_query_result_cache(sess: &Session) -> Option<OnDiskCache> {
190    if sess.opts.incremental.is_none() {
191        return None;
192    }
193
194    let _prof_timer = sess.prof.generic_activity("incr_comp_load_query_result_cache");
195
196    let path = query_cache_path(sess);
197    match load_data(&path, sess) {
198        LoadResult::Ok { data: (bytes, start_pos) } => {
199            let cache = OnDiskCache::new(sess, bytes, start_pos).unwrap_or_else(|()| {
200                sess.dcx().emit_warn(errors::CorruptFile { path: &path });
201                OnDiskCache::new_empty()
202            });
203            Some(cache)
204        }
205        _ => Some(OnDiskCache::new_empty()),
206    }
207}
208
209/// Setups the dependency graph by loading an existing graph from disk and set up streaming of a
210/// new graph to an incremental session directory.
211pub fn setup_dep_graph(sess: &Session, crate_name: Symbol, deps: &DepsType) -> DepGraph {
212    // `load_dep_graph` can only be called after `prepare_session_directory`.
213    prepare_session_directory(sess, crate_name);
214
215    let res = sess.opts.build_dep_graph().then(|| load_dep_graph(sess, deps));
216
217    if sess.opts.incremental.is_some() {
218        sess.time("incr_comp_garbage_collect_session_directories", || {
219            if let Err(e) = garbage_collect_session_directories(sess) {
220                warn!(
221                    "Error while trying to garbage collect incremental \
222                     compilation cache directory: {}",
223                    e
224                );
225            }
226        });
227    }
228
229    res.and_then(|result| {
230        let (prev_graph, prev_work_products) = result.open(sess);
231        build_dep_graph(sess, prev_graph, prev_work_products)
232    })
233    .unwrap_or_else(DepGraph::new_disabled)
234}