Skip to main content

rustc_incremental/persist/
load.rs

1//! Code to load the dep-graph from files.
2
3use std::io;
4use std::path::PathBuf;
5use std::sync::Arc;
6
7use rustc_data_structures::unord::UnordMap;
8use rustc_hashes::Hash64;
9use rustc_middle::dep_graph::{DepGraph, SerializedDepGraph, WorkProductMap};
10use rustc_middle::query::on_disk_cache::OnDiskCache;
11use rustc_serialize::opaque::{FileEncoder, MemDecoder};
12use rustc_serialize::{Decodable, Encodable};
13use rustc_session::config::IncrementalStateAssertion;
14use rustc_session::{IncrCompSession, Session, StableCrateId};
15use rustc_span::Symbol;
16use tracing::{debug, warn};
17
18use super::data::*;
19use super::fs::*;
20use super::{file_format, work_product};
21use crate::diagnostics;
22use crate::persist::file_format::{OpenFile, OpenFileError};
23
24#[derive(#[automatically_derived]
impl ::core::fmt::Debug for LoadResult {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            LoadResult::Ok {
                prev_graph: __self_0, prev_work_products: __self_1 } =>
                ::core::fmt::Formatter::debug_struct_field2_finish(f, "Ok",
                    "prev_graph", __self_0, "prev_work_products", &__self_1),
            LoadResult::DataOutOfDate =>
                ::core::fmt::Formatter::write_str(f, "DataOutOfDate"),
            LoadResult::IoError { path: __self_0, err: __self_1 } =>
                ::core::fmt::Formatter::debug_struct_field2_finish(f,
                    "IoError", "path", __self_0, "err", &__self_1),
        }
    }
}Debug)]
25/// Represents the result of an attempt to load incremental compilation data.
26enum LoadResult {
27    /// Loading was successful.
28    Ok { prev_graph: Arc<SerializedDepGraph>, prev_work_products: WorkProductMap },
29    /// The file either didn't exist or was produced by an incompatible compiler version.
30    DataOutOfDate,
31    /// Loading failed due to an unexpected I/O error.
32    IoError { path: PathBuf, err: io::Error },
33}
34
35fn delete_dirty_work_product(
36    sess: &Session,
37    incr_comp_session: &IncrCompSession,
38    swp: SerializedWorkProduct,
39) {
40    {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_incremental/src/persist/load.rs:40",
                        "rustc_incremental::persist::load", ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_incremental/src/persist/load.rs"),
                        ::tracing_core::__macro_support::Option::Some(40u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_incremental::persist::load"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("delete_dirty_work_product({0:?})",
                                                    swp) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!("delete_dirty_work_product({:?})", swp);
41    work_product::delete_workproduct_files(sess, incr_comp_session, &swp.work_product);
42}
43
44fn load_dep_graph(sess: &Session, incr_comp_session: &IncrCompSession) -> LoadResult {
45    if !sess.opts.incremental.is_some() {
    ::core::panicking::panic("assertion failed: sess.opts.incremental.is_some()")
};assert!(sess.opts.incremental.is_some());
46
47    let _timer = sess.prof.generic_activity("incr_comp_prepare_load_dep_graph");
48
49    // Calling `sess.incr_comp_session_dir()` will panic if `sess.opts.incremental.is_none()`.
50    // Fortunately, we just checked that this isn't the case.
51    let path = dep_graph_path(incr_comp_session);
52    let expected_hash = sess.opts.dep_tracking_hash(false);
53
54    let mut prev_work_products = UnordMap::default();
55
56    let work_products_path = work_products_path(incr_comp_session);
57
58    if let Ok(OpenFile { mmap, start_pos }) =
59        file_format::open_incremental_file(sess, &work_products_path)
60    {
61        // Decode the list of work_products
62        let Ok(mut work_product_decoder) = MemDecoder::new(&mmap[..], start_pos) else {
63            sess.dcx().emit_warn(diagnostics::CorruptFile { path: &work_products_path });
64            return LoadResult::DataOutOfDate;
65        };
66        let work_products: Vec<SerializedWorkProduct> =
67            Decodable::decode(&mut work_product_decoder);
68
69        for swp in work_products {
70            let all_files_exist = swp.work_product.saved_files.items().all(|(_, path)| {
71                let exists = in_incr_comp_dir_sess(incr_comp_session, path).exists();
72                if !exists && sess.opts.unstable_opts.incremental_info {
73                    {
    ::std::io::_eprint(format_args!("incremental: could not find file for work product: {0}\n",
            path));
};eprintln!("incremental: could not find file for work product: {path}",);
74                }
75                exists
76            });
77
78            if all_files_exist {
79                {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_incremental/src/persist/load.rs:79",
                        "rustc_incremental::persist::load", ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_incremental/src/persist/load.rs"),
                        ::tracing_core::__macro_support::Option::Some(79u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_incremental::persist::load"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("reconcile_work_products: all files for {0:?} exist",
                                                    swp) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!("reconcile_work_products: all files for {:?} exist", swp);
80                prev_work_products.insert(swp.id, swp.work_product);
81            } else {
82                {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_incremental/src/persist/load.rs:82",
                        "rustc_incremental::persist::load", ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_incremental/src/persist/load.rs"),
                        ::tracing_core::__macro_support::Option::Some(82u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_incremental::persist::load"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("reconcile_work_products: some file for {0:?} does not exist",
                                                    swp) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!("reconcile_work_products: some file for {:?} does not exist", swp);
83                delete_dirty_work_product(sess, incr_comp_session, swp);
84            }
85        }
86    }
87
88    let _prof_timer = sess.prof.generic_activity("incr_comp_load_dep_graph");
89
90    match file_format::open_incremental_file(sess, &path) {
91        Err(OpenFileError::NotFoundOrHeaderMismatch) => LoadResult::DataOutOfDate,
92        Err(OpenFileError::IoError { err }) => LoadResult::IoError { path: path.to_owned(), err },
93        Ok(OpenFile { mmap, start_pos }) => {
94            let Ok(mut decoder) = MemDecoder::new(&mmap, start_pos) else {
95                sess.dcx().emit_warn(diagnostics::CorruptFile { path: &path });
96                return LoadResult::DataOutOfDate;
97            };
98            let prev_commandline_args_hash = Hash64::decode(&mut decoder);
99
100            if prev_commandline_args_hash != expected_hash {
101                if sess.opts.unstable_opts.incremental_info {
102                    {
    ::std::io::_eprint(format_args!("[incremental] completely ignoring cache because of differing commandline arguments\n"));
};eprintln!(
103                        "[incremental] completely ignoring cache because of \
104                                    differing commandline arguments"
105                    );
106                }
107                // We can't reuse the cache, purge it.
108                {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_incremental/src/persist/load.rs:108",
                        "rustc_incremental::persist::load", ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_incremental/src/persist/load.rs"),
                        ::tracing_core::__macro_support::Option::Some(108u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_incremental::persist::load"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("load_dep_graph_new: differing commandline arg hashes")
                                            as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!("load_dep_graph_new: differing commandline arg hashes");
109
110                // No need to do any further work
111                return LoadResult::DataOutOfDate;
112            }
113
114            let prev_graph = SerializedDepGraph::decode(&mut decoder, &sess.prof);
115
116            LoadResult::Ok { prev_graph, prev_work_products }
117        }
118    }
119}
120
121/// Attempts to load the query result cache from disk
122///
123/// If we are not in incremental compilation mode, returns `None`.
124/// Otherwise, tries to load the query result cache from disk,
125/// creating an empty cache if it could not be loaded.
126pub fn load_query_result_cache(
127    sess: &Session,
128    incr_comp_session: Option<&IncrCompSession>,
129) -> Option<OnDiskCache> {
130    if sess.opts.incremental.is_none() {
131        return None;
132    }
133    let incr_comp_session = incr_comp_session.unwrap();
134
135    let _prof_timer = sess.prof.generic_activity("incr_comp_load_query_result_cache");
136
137    let path = query_cache_path(incr_comp_session);
138    match file_format::open_incremental_file(sess, &path) {
139        Ok(OpenFile { mmap, start_pos }) => {
140            let cache = OnDiskCache::new(sess, mmap, start_pos).unwrap_or_else(|()| {
141                sess.dcx().emit_warn(diagnostics::CorruptFile { path: &path });
142                OnDiskCache::new_empty()
143            });
144            Some(cache)
145        }
146        Err(OpenFileError::NotFoundOrHeaderMismatch | OpenFileError::IoError { .. }) => {
147            Some(OnDiskCache::new_empty())
148        }
149    }
150}
151
152/// Emits a fatal error if the assertion in `-Zassert-incr-state` doesn't match
153/// the outcome of trying to load previous-session state.
154fn maybe_assert_incr_state(sess: &Session, load_result: &LoadResult) {
155    // Return immediately if there's nothing to assert.
156    let Some(assertion) = sess.opts.unstable_opts.assert_incr_state else { return };
157
158    // Match exhaustively to make sure we don't miss any cases.
159    let loaded = match load_result {
160        LoadResult::Ok { .. } => true,
161        LoadResult::DataOutOfDate | LoadResult::IoError { .. } => false,
162    };
163
164    match assertion {
165        IncrementalStateAssertion::Loaded => {
166            if !loaded {
167                sess.dcx().emit_fatal(diagnostics::AssertLoaded);
168            }
169        }
170        IncrementalStateAssertion::NotLoaded => {
171            if loaded {
172                sess.dcx().emit_fatal(diagnostics::AssertNotLoaded)
173            }
174        }
175    }
176}
177
178/// Loads the previous session's dependency graph from disk if possible, and
179/// sets up streaming output for the current session's dep graph data into an
180/// incremental session directory.
181///
182/// In non-incremental mode, a dummy dep graph is returned immediately.
183pub fn setup_dep_graph(
184    sess: &Session,
185    crate_name: Symbol,
186    stable_crate_id: StableCrateId,
187) -> (DepGraph, Option<IncrCompSession>) {
188    if sess.opts.incremental.is_none() {
189        return (DepGraph::new_disabled(), None);
190    }
191
192    // `load_dep_graph` can only be called after `prepare_session_directory`.
193    let incr_comp_session = prepare_session_directory(sess, crate_name, stable_crate_id);
194    // Try to load the previous session's dep graph and work products.
195    let load_result = load_dep_graph(sess, &incr_comp_session);
196
197    sess.time("incr_comp_garbage_collect_session_directories", || {
198        if let Err(e) =
199            garbage_collect_session_directories(sess, &incr_comp_session.session_directory)
200        {
201            {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_incremental/src/persist/load.rs:201",
                        "rustc_incremental::persist::load", ::tracing::Level::WARN,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_incremental/src/persist/load.rs"),
                        ::tracing_core::__macro_support::Option::Some(201u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_incremental::persist::load"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::WARN <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::WARN <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("Error while trying to garbage collect incremental compilation cache directory: {0}",
                                                    e) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};warn!(
202                "Error while trying to garbage collect incremental compilation \
203                cache directory: {e}",
204            );
205        }
206    });
207
208    // Emit a fatal error if `-Zassert-incr-state` is present and unsatisfied.
209    maybe_assert_incr_state(sess, &load_result);
210
211    let (prev_graph, prev_work_products) = match load_result {
212        LoadResult::IoError { path, err } => {
213            sess.dcx().emit_warn(diagnostics::LoadDepGraph { path, err });
214            Default::default()
215        }
216        LoadResult::DataOutOfDate => {
217            if let Err(err) = delete_all_session_dir_contents(&incr_comp_session) {
218                sess.dcx().emit_err(diagnostics::DeleteIncompatible {
219                    path: dep_graph_path(&incr_comp_session),
220                    err,
221                });
222            }
223            Default::default()
224        }
225        LoadResult::Ok { prev_graph, prev_work_products } => (prev_graph, prev_work_products),
226    };
227
228    // Stream the dep-graph to an alternate file, to avoid overwriting anything in case of errors.
229    let path_buf = staging_dep_graph_path(&incr_comp_session);
230
231    let mut encoder = FileEncoder::new(&path_buf).unwrap_or_else(|err| {
232        // We're in incremental mode but couldn't set up streaming output of the dep graph.
233        // Exit immediately instead of continuing in an inconsistent and untested state.
234        sess.dcx().emit_fatal(diagnostics::CreateDepGraph { path: &path_buf, err })
235    });
236
237    file_format::write_file_header(&mut encoder, sess);
238
239    // First encode the commandline arguments hash
240    sess.opts.dep_tracking_hash(false).encode(&mut encoder);
241
242    (DepGraph::new(sess, prev_graph, prev_work_products, encoder), Some(incr_comp_session))
243}