| //! Code to load the dep-graph from files. |
| |
| use std::io; |
| use std::path::PathBuf; |
| use std::sync::Arc; |
| |
| use rustc_data_structures::unord::UnordMap; |
| use rustc_hashes::Hash64; |
| use rustc_middle::dep_graph::{DepGraph, SerializedDepGraph, WorkProductMap}; |
| use rustc_middle::query::on_disk_cache::OnDiskCache; |
| use rustc_serialize::opaque::{FileEncoder, MemDecoder}; |
| use rustc_serialize::{Decodable, Encodable}; |
| use rustc_session::config::IncrementalStateAssertion; |
| use rustc_session::{Session, StableCrateId}; |
| use rustc_span::Symbol; |
| use tracing::{debug, warn}; |
| |
| use super::data::*; |
| use super::fs::*; |
| use super::{file_format, work_product}; |
| use crate::errors; |
| use crate::persist::file_format::{OpenFile, OpenFileError}; |
| |
| #[derive(Debug)] |
| /// Represents the result of an attempt to load incremental compilation data. |
| enum LoadResult { |
| /// Loading was successful. |
| Ok { prev_graph: Arc<SerializedDepGraph>, prev_work_products: WorkProductMap }, |
| /// The file either didn't exist or was produced by an incompatible compiler version. |
| DataOutOfDate, |
| /// Loading failed due to an unexpected I/O error. |
| IoError { path: PathBuf, err: io::Error }, |
| } |
| |
| fn delete_dirty_work_product(sess: &Session, swp: SerializedWorkProduct) { |
| debug!("delete_dirty_work_product({:?})", swp); |
| work_product::delete_workproduct_files(sess, &swp.work_product); |
| } |
| |
| fn load_dep_graph(sess: &Session) -> LoadResult { |
| assert!(sess.opts.incremental.is_some()); |
| |
| let _timer = sess.prof.generic_activity("incr_comp_prepare_load_dep_graph"); |
| |
| // Calling `sess.incr_comp_session_dir()` will panic if `sess.opts.incremental.is_none()`. |
| // Fortunately, we just checked that this isn't the case. |
| let path = dep_graph_path(sess); |
| let expected_hash = sess.opts.dep_tracking_hash(false); |
| |
| let mut prev_work_products = UnordMap::default(); |
| |
| // If we are only building with -Zquery-dep-graph but without an actual |
| // incr. comp. session directory, we skip this. Otherwise we'd fail |
| // when trying to load work products. |
| if sess.incr_comp_session_dir_opt().is_some() { |
| let work_products_path = work_products_path(sess); |
| |
| if let Ok(OpenFile { mmap, start_pos }) = |
| file_format::open_incremental_file(sess, &work_products_path) |
| { |
| // Decode the list of work_products |
| let Ok(mut work_product_decoder) = MemDecoder::new(&mmap[..], start_pos) else { |
| sess.dcx().emit_warn(errors::CorruptFile { path: &work_products_path }); |
| return LoadResult::DataOutOfDate; |
| }; |
| let work_products: Vec<SerializedWorkProduct> = |
| Decodable::decode(&mut work_product_decoder); |
| |
| for swp in work_products { |
| let all_files_exist = swp.work_product.saved_files.items().all(|(_, path)| { |
| let exists = in_incr_comp_dir_sess(sess, path).exists(); |
| if !exists && sess.opts.unstable_opts.incremental_info { |
| eprintln!("incremental: could not find file for work product: {path}",); |
| } |
| exists |
| }); |
| |
| if all_files_exist { |
| debug!("reconcile_work_products: all files for {:?} exist", swp); |
| prev_work_products.insert(swp.id, swp.work_product); |
| } else { |
| debug!("reconcile_work_products: some file for {:?} does not exist", swp); |
| delete_dirty_work_product(sess, swp); |
| } |
| } |
| } |
| } |
| |
| let _prof_timer = sess.prof.generic_activity("incr_comp_load_dep_graph"); |
| |
| match file_format::open_incremental_file(sess, &path) { |
| Err(OpenFileError::NotFoundOrHeaderMismatch) => LoadResult::DataOutOfDate, |
| Err(OpenFileError::IoError { err }) => LoadResult::IoError { path: path.to_owned(), err }, |
| Ok(OpenFile { mmap, start_pos }) => { |
| let Ok(mut decoder) = MemDecoder::new(&mmap, start_pos) else { |
| sess.dcx().emit_warn(errors::CorruptFile { path: &path }); |
| return LoadResult::DataOutOfDate; |
| }; |
| let prev_commandline_args_hash = Hash64::decode(&mut decoder); |
| |
| if prev_commandline_args_hash != expected_hash { |
| if sess.opts.unstable_opts.incremental_info { |
| eprintln!( |
| "[incremental] completely ignoring cache because of \ |
| differing commandline arguments" |
| ); |
| } |
| // We can't reuse the cache, purge it. |
| debug!("load_dep_graph_new: differing commandline arg hashes"); |
| |
| // No need to do any further work |
| return LoadResult::DataOutOfDate; |
| } |
| |
| let prev_graph = SerializedDepGraph::decode(&mut decoder); |
| |
| LoadResult::Ok { prev_graph, prev_work_products } |
| } |
| } |
| } |
| |
| /// Attempts to load the query result cache from disk |
| /// |
| /// If we are not in incremental compilation mode, returns `None`. |
| /// Otherwise, tries to load the query result cache from disk, |
| /// creating an empty cache if it could not be loaded. |
| pub fn load_query_result_cache(sess: &Session) -> Option<OnDiskCache> { |
| if sess.opts.incremental.is_none() { |
| return None; |
| } |
| |
| let _prof_timer = sess.prof.generic_activity("incr_comp_load_query_result_cache"); |
| |
| let path = query_cache_path(sess); |
| match file_format::open_incremental_file(sess, &path) { |
| Ok(OpenFile { mmap, start_pos }) => { |
| let cache = OnDiskCache::new(sess, mmap, start_pos).unwrap_or_else(|()| { |
| sess.dcx().emit_warn(errors::CorruptFile { path: &path }); |
| OnDiskCache::new_empty() |
| }); |
| Some(cache) |
| } |
| Err(OpenFileError::NotFoundOrHeaderMismatch | OpenFileError::IoError { .. }) => { |
| Some(OnDiskCache::new_empty()) |
| } |
| } |
| } |
| |
| /// Emits a fatal error if the assertion in `-Zassert-incr-state` doesn't match |
| /// the outcome of trying to load previous-session state. |
| fn maybe_assert_incr_state(sess: &Session, load_result: &LoadResult) { |
| // Return immediately if there's nothing to assert. |
| let Some(assertion) = sess.opts.unstable_opts.assert_incr_state else { return }; |
| |
| // Match exhaustively to make sure we don't miss any cases. |
| let loaded = match load_result { |
| LoadResult::Ok { .. } => true, |
| LoadResult::DataOutOfDate | LoadResult::IoError { .. } => false, |
| }; |
| |
| match assertion { |
| IncrementalStateAssertion::Loaded => { |
| if !loaded { |
| sess.dcx().emit_fatal(errors::AssertLoaded); |
| } |
| } |
| IncrementalStateAssertion::NotLoaded => { |
| if loaded { |
| sess.dcx().emit_fatal(errors::AssertNotLoaded) |
| } |
| } |
| } |
| } |
| |
| /// Loads the previous session's dependency graph from disk if possible, and |
| /// sets up streaming output for the current session's dep graph data into an |
| /// incremental session directory. |
| /// |
| /// In non-incremental mode, a dummy dep graph is returned immediately. |
| pub fn setup_dep_graph( |
| sess: &Session, |
| crate_name: Symbol, |
| stable_crate_id: StableCrateId, |
| ) -> DepGraph { |
| if sess.opts.incremental.is_none() { |
| return DepGraph::new_disabled(); |
| } |
| |
| // `load_dep_graph` can only be called after `prepare_session_directory`. |
| prepare_session_directory(sess, crate_name, stable_crate_id); |
| // Try to load the previous session's dep graph and work products. |
| let load_result = load_dep_graph(sess); |
| |
| sess.time("incr_comp_garbage_collect_session_directories", || { |
| if let Err(e) = garbage_collect_session_directories(sess) { |
| warn!( |
| "Error while trying to garbage collect incremental compilation \ |
| cache directory: {e}", |
| ); |
| } |
| }); |
| |
| // Emit a fatal error if `-Zassert-incr-state` is present and unsatisfied. |
| maybe_assert_incr_state(sess, &load_result); |
| |
| let (prev_graph, prev_work_products) = match load_result { |
| LoadResult::IoError { path, err } => { |
| sess.dcx().emit_warn(errors::LoadDepGraph { path, err }); |
| Default::default() |
| } |
| LoadResult::DataOutOfDate => { |
| if let Err(err) = delete_all_session_dir_contents(sess) { |
| sess.dcx().emit_err(errors::DeleteIncompatible { path: dep_graph_path(sess), err }); |
| } |
| Default::default() |
| } |
| LoadResult::Ok { prev_graph, prev_work_products } => (prev_graph, prev_work_products), |
| }; |
| |
| // Stream the dep-graph to an alternate file, to avoid overwriting anything in case of errors. |
| let path_buf = staging_dep_graph_path(sess); |
| |
| let mut encoder = FileEncoder::new(&path_buf).unwrap_or_else(|err| { |
| // We're in incremental mode but couldn't set up streaming output of the dep graph. |
| // Exit immediately instead of continuing in an inconsistent and untested state. |
| sess.dcx().emit_fatal(errors::CreateDepGraph { path: &path_buf, err }) |
| }); |
| |
| file_format::write_file_header(&mut encoder, sess); |
| |
| // First encode the commandline arguments hash |
| sess.opts.dep_tracking_hash(false).encode(&mut encoder); |
| |
| DepGraph::new(sess, prev_graph, prev_work_products, encoder) |
| } |