From 27ed2eda1298d9986a77c751ce6fa7adcaf09650 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Fri, 18 Sep 2026 12:10:15 +0200 Subject: [PATCH 1/3] Introduce LockedDir type --- compiler/rustc_codegen_ssa/src/back/write.rs | 2 +- compiler/rustc_data_structures/src/flock.rs | 31 ++++++++++++- compiler/rustc_incremental/src/persist/fs.rs | 48 +++++++++++--------- compiler/rustc_session/src/session.rs | 6 +-- 4 files changed, 58 insertions(+), 29 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index c93aa87b25077..0f609464593dc 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -1297,7 +1297,7 @@ fn start_executing_work( incr_comp_session_dir: tcx .incr_comp_session .as_ref() - .map(|incr_comp_session| incr_comp_session.session_directory.clone()), + .map(|incr_comp_session| (&*incr_comp_session.session_directory).to_owned()), output_filenames: Arc::clone(tcx.output_filenames(())), module_config: regular_config, opt_level, diff --git a/compiler/rustc_data_structures/src/flock.rs b/compiler/rustc_data_structures/src/flock.rs index 9bd7d06c27f5e..deb779a4996dd 100644 --- a/compiler/rustc_data_structures/src/flock.rs +++ b/compiler/rustc_data_structures/src/flock.rs @@ -6,7 +6,8 @@ use std::fs::{File, OpenOptions}; use std::io; -use std::path::Path; +use std::ops::Deref; +use std::path::{Path, PathBuf}; #[derive(Debug)] pub enum Lock { @@ -68,3 +69,31 @@ cfg_select! { use unsupported as fallback; } } + +/// A directory together with a locked lockfile. +pub struct LockedDir { + dir: PathBuf, + /// `_lock_file` is never directly used, but its presence + /// alone has an effect, because the file will unlock when the session is + /// dropped. + _lock_file: Lock, +} + +impl LockedDir { + pub fn try_lock( + dir: PathBuf, + lock_file: &Path, + create: bool, + exclusive: bool, + ) -> io::Result { + Ok(LockedDir { dir, _lock_file: Lock::try_lock(lock_file, create, exclusive)? }) + } +} + +impl Deref for LockedDir { + type Target = Path; + + fn deref(&self) -> &Path { + &self.dir + } +} diff --git a/compiler/rustc_incremental/src/persist/fs.rs b/compiler/rustc_incremental/src/persist/fs.rs index 504e93cf2aaef..3096b44eafb44 100644 --- a/compiler/rustc_incremental/src/persist/fs.rs +++ b/compiler/rustc_incremental/src/persist/fs.rs @@ -236,16 +236,13 @@ pub(crate) fn prepare_session_directory( // Generate a session directory of the form: // // {incr-comp-dir}/{crate-name-and-disambiguator}/s-{timestamp}-{random}-working - let session_dir = generate_session_dir_path(&crate_dir); - debug!("session-dir: {}", session_dir.display()); + let session_directory = generate_session_dir_path(&crate_dir); + debug!("session-dir: {}", session_directory.display()); // Lock the new session directory. If this fails, return an // error without retrying - let (directory_lock, lock_file_path) = lock_directory(sess, &session_dir); - - // Now that we have the lock, we can actually create the session - // directory - create_dir(sess, &session_dir, "session"); + let (session_directory, lock_file_path) = + lock_and_create_directory(sess, &session_directory); // Find a suitable source directory to copy from. Ignore those that we // have already tried before. @@ -258,20 +255,20 @@ pub(crate) fn prepare_session_directory( directory." ); - return IncrCompSession { session_directory: session_dir, _lock_file: directory_lock }; + return IncrCompSession { session_directory }; }; debug!("attempting to copy data from source: {}", source_directory.display()); // Try copying over all files from the source directory - if let Ok(allows_links) = copy_files(sess, &session_dir, &source_directory) { + if let Ok(allows_links) = copy_files(sess, &session_directory, &source_directory) { debug!("successfully copied data from: {}", source_directory.display()); if !allows_links { - sess.dcx().emit_warn(diagnostics::HardLinkFailed { path: &session_dir }); + sess.dcx().emit_warn(diagnostics::HardLinkFailed { path: &session_directory }); } - return IncrCompSession { session_directory: session_dir, _lock_file: directory_lock }; + return IncrCompSession { session_directory }; } else { debug!("copying failed - trying next directory"); @@ -281,12 +278,12 @@ pub(crate) fn prepare_session_directory( // Try to remove the session directory we just allocated. We don't // know if there's any garbage in it from the failed copy action. - if let Err(err) = std_fs::remove_dir_all(&session_dir) { - sess.dcx().emit_warn(diagnostics::DeletePartial { path: &session_dir, err }); + if let Err(err) = std_fs::remove_dir_all(&*session_directory) { + sess.dcx().emit_warn(diagnostics::DeletePartial { path: &session_directory, err }); } delete_session_dir_lock_file(sess, &lock_file_path); - drop(directory_lock); + drop(session_directory); } } } @@ -310,7 +307,7 @@ pub fn finalize_session_directory( let _timer = sess.timer("incr_comp_finalize_session_directory"); - let incr_comp_session_dir = incr_comp_session.session_directory.clone(); + let incr_comp_session_dir = &*incr_comp_session.session_directory; debug!("finalize_session_directory() - session directory: {}", incr_comp_session_dir.display()); @@ -334,7 +331,7 @@ pub fn finalize_session_directory( let new_path = incr_comp_session_dir.parent().unwrap().join(&*sub_dir_name); debug!("finalize_session_directory() - new path: {}", new_path.display()); - let result = std_fs::rename(&*incr_comp_session_dir, &new_path).or_else(|e| { + let result = std_fs::rename(incr_comp_session_dir, &new_path).or_else(|e| { if !cfg!(windows) || e.kind() != ErrorKind::PermissionDenied { return Err(e); } @@ -355,7 +352,7 @@ pub fn finalize_session_directory( debug!("finalize_session_directory() - error replacing hard link with copy: {}", err); } - rename_path_with_retry(&*incr_comp_session_dir, &new_path, 3) + rename_path_with_retry(incr_comp_session_dir, &new_path, 3) }); match result { @@ -364,7 +361,7 @@ pub fn finalize_session_directory( } Err(e) => { // Warn about the error. However, no need to abort compilation now. - sess.dcx().emit_note(diagnostics::Finalize { path: &incr_comp_session_dir, err: e }); + sess.dcx().emit_note(diagnostics::Finalize { path: incr_comp_session_dir, err: e }); debug!("finalize_session_directory() - error"); } @@ -471,18 +468,25 @@ fn create_dir(sess: &Session, path: &Path, dir_tag: &str) { } } -/// Allocate the lock-file and lock it. -fn lock_directory(sess: &Session, session_dir: &Path) -> (flock::Lock, PathBuf) { +/// Allocate the lock-file, lock it and create the session directory. +fn lock_and_create_directory(sess: &Session, session_dir: &Path) -> (flock::LockedDir, PathBuf) { let lock_file_path = lock_file_path(session_dir); debug!("lock_directory() - lock_file: {}", lock_file_path.display()); - match flock::Lock::try_lock( + match flock::LockedDir::try_lock( + session_dir.to_owned(), &lock_file_path, true, // create the lock file true, ) { // the lock should be exclusive - Ok(lock) => (lock, lock_file_path), + Ok(lock) => { + // Now that we have the lock, we can actually create the session + // directory + create_dir(sess, &session_dir, "session"); + + (lock, lock_file_path) + } Err(lock_err) => { let is_unsupported_lock = flock::Lock::error_unsupported(&lock_err); sess.dcx().emit_fatal(diagnostics::CreateLock { diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index 274cfe8d7eb8a..acfbb08b9b630 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -1844,11 +1844,7 @@ pub struct IncrCompSession { /// The directory containing all cached data. Cached data from a previous /// session can be read out of it and new data for the current session will /// be written into it. - pub session_directory: PathBuf, - /// `_lock_file` is never directly used, but its presence - /// alone has an effect, because the file will unlock when the session is - /// dropped. - pub _lock_file: flock::Lock, + pub session_directory: flock::LockedDir, } /// A wrapper around an [`DiagCtxt`] that is used for early error emissions. From 6e7ebbe8919d7f991e8a77eedb845dc11c66f959 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Fri, 18 Sep 2026 12:34:05 +0200 Subject: [PATCH 2/3] Depend on lockfiles to prevent GC of the current session Instead of manually skipping it right before the remove_dir. This will be simpler once we stop immediately copying from the old incr comp dir. Previously this wasn't possible due to the usage of process-based fcntl locking, but I added proper fd-based locking recently. --- compiler/rustc_incremental/src/persist/fs.rs | 94 +++++++------------ .../rustc_incremental/src/persist/fs/tests.rs | 7 +- .../rustc_incremental/src/persist/load.rs | 8 +- .../run-make/incremental-session-gc/rmake.rs | 8 +- 4 files changed, 47 insertions(+), 70 deletions(-) diff --git a/compiler/rustc_incremental/src/persist/fs.rs b/compiler/rustc_incremental/src/persist/fs.rs index 3096b44eafb44..374e0b0dd6d1a 100644 --- a/compiler/rustc_incremental/src/persist/fs.rs +++ b/compiler/rustc_incremental/src/persist/fs.rs @@ -367,9 +367,11 @@ pub fn finalize_session_directory( } } - drop(incr_comp_session); // Unlock incr comp session dir - - let _ = garbage_collect_session_directories(sess, &new_path); + let _ = garbage_collect_session_directories( + sess, + &incr_comp_session, + false, // keep_most_recent + ); } pub(crate) fn delete_all_session_dir_contents( @@ -616,10 +618,13 @@ fn is_old_enough_to_be_collected(timestamp: SystemTime) -> bool { /// Runs garbage collection for the current session. pub(crate) fn garbage_collect_session_directories( sess: &Session, - session_directory: &Path, + incr_comp_session: &IncrCompSession, + keep_most_recent: bool, ) -> io::Result<()> { debug!("garbage_collect_session_directories() - begin"); + let session_directory = &*incr_comp_session.session_directory; + debug!( "garbage_collect_session_directories() - session directory: {}", session_directory.display() @@ -721,9 +726,6 @@ pub(crate) fn garbage_collect_session_directories( } } - let current_session_directory_name = - session_directory.file_name().expect("session directory is not `..`"); - // Now garbage collect the valid session directories. let deletion_candidates = lock_file_to_session_dir.items().filter_map(|(lock_file_name, directory_name)| { @@ -771,31 +773,6 @@ pub(crate) fn garbage_collect_session_directories( } } } else if is_old_enough_to_be_collected(timestamp) { - if directory_name.as_str() == current_session_directory_name { - // Skipping our own active directory is important for correctness. - // - // To summarize #147821: we will try to lock directories before deciding they can be - // garbage collected, but the ability of `flock::Lock` to detect a lock held *by the - // same process* varies across file locking APIs. Then, if our own session directory - // has become old enough to be eligible for GC, we are beholden to platform-specific - // details about detecting the our own lock on the session directory. - // - // POSIX `fcntl(F_SETLK)`-style file locks are maintained across a process. On - // systems where this is the mechanism for `flock::Lock`, there is no way to - // discover if an `flock::Lock` has been created in the same process on the same - // file. Attempting to set a lock on the lockfile again will succeed, even if the - // lock was set by another thread, on another file descriptor. Then we would - // garbage collect our own live directory, unable to tell it was locked perhaps by - // this same thread. - // - // It's not clear that `flock::Lock` can be fixed for this in general, and our own - // incremental session directory is the only one which this process may own, so skip - // it here and avoid the problem. We know it's not garbage anyway: we're using it. - // Once finalized, its lock is released. Include it in collection so we keep - // the newest completed session. - return None; - } - // When cleaning out "-working" session directories, i.e. // session directories that might still be in use by another // compiler instance, we only look a directories that are @@ -844,24 +821,22 @@ pub(crate) fn garbage_collect_session_directories( let deletion_candidates = deletion_candidates.into(); // Delete all but the most recent of the candidates - all_except_most_recent(deletion_candidates).into_items().all(|(path, lock)| { - if path.file_name() == Some(current_session_directory_name) { - return true; - } + all_except_maybe_most_recent(deletion_candidates, keep_most_recent).into_items().all( + |(path, lock)| { + debug!("garbage_collect_session_directories() - deleting `{}`", path.display()); - debug!("garbage_collect_session_directories() - deleting `{}`", path.display()); - - if let Err(err) = std_fs::remove_dir_all(&path) { - sess.dcx().emit_warn(diagnostics::FinalizedGcFailed { path: &path, err }); - } else { - delete_session_dir_lock_file(sess, &lock_file_path(&path)); - } + if let Err(err) = std_fs::remove_dir_all(&path) { + sess.dcx().emit_warn(diagnostics::FinalizedGcFailed { path: &path, err }); + } else { + delete_session_dir_lock_file(sess, &lock_file_path(&path)); + } - // Let's make it explicit that the file lock is released at this point, - // or rather, that we held on to it until here - drop(lock); - true - }); + // Let's make it explicit that the file lock is released at this point, + // or rather, that we held on to it until here + drop(lock); + true + }, + ); Ok(()) } @@ -876,20 +851,19 @@ fn delete_old(sess: &Session, path: &Path) { } } -fn all_except_most_recent( +fn all_except_maybe_most_recent( deletion_candidates: UnordMap<(SystemTime, PathBuf), Option>, + keep_most_recent: bool, ) -> UnordMap> { - let most_recent = deletion_candidates.items().map(|(&(timestamp, _), _)| timestamp).max(); - - if let Some(most_recent) = most_recent { - deletion_candidates - .into_items() - .filter(|&((timestamp, _), _)| timestamp != most_recent) - .map(|((_, path), lock)| (path, lock)) - .collect() - } else { - UnordMap::default() - } + let most_recent = keep_most_recent + .then(|| deletion_candidates.items().map(|(&(timestamp, _), _)| timestamp).max()) + .flatten(); + + deletion_candidates + .into_items() + .filter(|&((timestamp, _), _)| Some(timestamp) != most_recent) + .map(|((_, path), lock)| (path, lock)) + .collect() } fn safe_remove_file(p: &Path) -> io::Result<()> { diff --git a/compiler/rustc_incremental/src/persist/fs/tests.rs b/compiler/rustc_incremental/src/persist/fs/tests.rs index 3652656b7c48f..75b573dd4944a 100644 --- a/compiler/rustc_incremental/src/persist/fs/tests.rs +++ b/compiler/rustc_incremental/src/persist/fs/tests.rs @@ -10,11 +10,14 @@ fn test_all_except_most_recent() { ((UNIX_EPOCH + Duration::new(2, 0), PathBuf::from("2")), None), ]); assert_eq!( - all_except_most_recent(input).into_items().map(|(path, _)| path).into_sorted_stable_ord(), + all_except_maybe_most_recent(input, true) + .into_items() + .map(|(path, _)| path) + .into_sorted_stable_ord(), vec![PathBuf::from("1"), PathBuf::from("2"), PathBuf::from("3"), PathBuf::from("4")] ); - assert!(all_except_most_recent(UnordMap::default()).is_empty()); + assert!(all_except_maybe_most_recent(UnordMap::default(), true).is_empty()); } #[test] diff --git a/compiler/rustc_incremental/src/persist/load.rs b/compiler/rustc_incremental/src/persist/load.rs index 3cf08961ed7b3..fb83149e65b22 100644 --- a/compiler/rustc_incremental/src/persist/load.rs +++ b/compiler/rustc_incremental/src/persist/load.rs @@ -195,9 +195,11 @@ pub fn setup_dep_graph( let load_result = load_dep_graph(sess, &incr_comp_session); sess.time("incr_comp_garbage_collect_session_directories", || { - if let Err(e) = - garbage_collect_session_directories(sess, &incr_comp_session.session_directory) - { + if let Err(e) = garbage_collect_session_directories( + sess, + &incr_comp_session, + true, // keep_most_recent + ) { warn!( "Error while trying to garbage collect incremental compilation \ cache directory: {e}", diff --git a/tests/run-make/incremental-session-gc/rmake.rs b/tests/run-make/incremental-session-gc/rmake.rs index dd4b300a5ced0..341a21690f215 100644 --- a/tests/run-make/incremental-session-gc/rmake.rs +++ b/tests/run-make/incremental-session-gc/rmake.rs @@ -33,11 +33,9 @@ fn main() { ); compile(); - let sessions = shallow_find_directories(crate_dir, |_| true); - assert_eq!(sessions.len(), 2, "{sessions:?}"); - assert!(sessions.contains(&newer)); - let current = sessions.into_iter().find(|session| *session != newer).unwrap(); - assert!(!current.file_name().unwrap().to_str().unwrap().ends_with("-working")); + let current = session_dir(); + assert_ne!(previous, newer); + assert!(!newer.exists(), "superseded session was not collected: {previous:?}"); assert_eq!(rfs::read_to_string(current.join("sentinel")), "previous session"); } From 49c263e609bf9f6209bb223a2dd484ad3382d79f Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Wed, 8 Jul 2026 18:03:01 +0000 Subject: [PATCH 3/3] Build a new incr comp session dir from scratch every time Rather than copying the old incr comp dir and then modifying it. This saves a copy/hardlink for files that are modified. And it removes the need for accurate work product tracking to avoid accumulating cruft, which is non-trivial. We don't accurately track the pre-LTO bitcode files for ThinLTO for example. --- .../rustc_codegen_cranelift/src/driver/aot.rs | 1 - compiler/rustc_codegen_llvm/src/back/lto.rs | 49 +-- compiler/rustc_codegen_ssa/src/back/write.rs | 50 +++- compiler/rustc_codegen_ssa/src/lib.rs | 2 - compiler/rustc_incremental/src/diagnostics.rs | 22 -- compiler/rustc_incremental/src/lib.rs | 3 +- compiler/rustc_incremental/src/persist/fs.rs | 280 +++++++----------- .../rustc_incremental/src/persist/fs/tests.rs | 27 +- .../rustc_incremental/src/persist/load.rs | 43 +-- compiler/rustc_incremental/src/persist/mod.rs | 2 +- .../rustc_incremental/src/persist/save.rs | 19 +- .../src/persist/work_product.rs | 24 +- compiler/rustc_interface/src/queries.rs | 1 - compiler/rustc_metadata/src/rmeta/encoder.rs | 5 +- compiler/rustc_session/src/session.rs | 9 +- .../run-make/incremental-session-gc/rmake.rs | 3 - 16 files changed, 197 insertions(+), 343 deletions(-) diff --git a/compiler/rustc_codegen_cranelift/src/driver/aot.rs b/compiler/rustc_codegen_cranelift/src/driver/aot.rs index cd82df386f5ce..73d4adf8e3cf2 100644 --- a/compiler/rustc_codegen_cranelift/src/driver/aot.rs +++ b/compiler/rustc_codegen_cranelift/src/driver/aot.rs @@ -140,7 +140,6 @@ fn emit_module( bytecode: None, assembly: None, llvm_ir: None, - links_from_incr_cache: Vec::new(), }) } diff --git a/compiler/rustc_codegen_llvm/src/back/lto.rs b/compiler/rustc_codegen_llvm/src/back/lto.rs index cb12c103834af..20a9a7a894c47 100644 --- a/compiler/rustc_codegen_llvm/src/back/lto.rs +++ b/compiler/rustc_codegen_llvm/src/back/lto.rs @@ -463,23 +463,34 @@ fn thin_lto( info!("thin LTO data created"); - let (key_map_path, prev_key_map, curr_key_map) = if let Some(ref incr_comp_session_dir) = - cgcx.incr_comp_session_dir - { - let path = incr_comp_session_dir.join(THIN_LTO_KEYS_INCR_COMP_FILE_NAME); - // If the previous file was deleted, or we get an IO error - // reading the file, then we'll just use `None` as the - // prev_key_map, which will force the code to be recompiled. - let prev = - if path.exists() { ThinLTOKeysMap::load_from_file(&path).ok() } else { None }; - let curr = ThinLTOKeysMap::from_thin_lto_modules(&data, &thin_modules, &module_names); - (Some(path), prev, curr) + let new_key_map_path = cgcx + .new_incr_comp_session_dir + .as_ref() + .map(|dir| dir.join(THIN_LTO_KEYS_INCR_COMP_FILE_NAME)); + + let prev_key_map = + if let Some(ref old_incr_comp_session_dir) = cgcx.old_incr_comp_session_dir { + let old_path = old_incr_comp_session_dir.join(THIN_LTO_KEYS_INCR_COMP_FILE_NAME); + + // If the previous file was deleted, or we get an IO error + // reading the file, then we'll just use `None` as the + // prev_key_map, which will force the code to be recompiled. + let prev = if old_path.exists() { + ThinLTOKeysMap::load_from_file(&old_path).ok() + } else { + None + }; + + prev + } else { + assert!(green_modules.is_empty()); + None + }; + let curr_key_map = if cgcx.new_incr_comp_session_dir.is_some() { + ThinLTOKeysMap::from_thin_lto_modules(&data, &thin_modules, &module_names) } else { - // If we don't compile incrementally, we don't need to load the - // import data from LLVM. assert!(green_modules.is_empty()); - let curr = ThinLTOKeysMap::default(); - (None, None, curr) + ThinLTOKeysMap::default() }; info!("thin LTO cache key map loaded"); info!("prev_key_map: {:#?}", prev_key_map); @@ -500,7 +511,8 @@ fn thin_lto( if let (Some(prev_key_map), true) = (prev_key_map.as_ref(), green_modules.contains_key(module_name)) { - assert!(cgcx.incr_comp_session_dir.is_some()); + assert!(cgcx.old_incr_comp_session_dir.is_some()); + assert!(cgcx.new_incr_comp_session_dir.is_some()); // If a module exists in both the current and the previous session, // and has the same LTO cache key in both sessions, then we can re-use it @@ -508,7 +520,6 @@ fn thin_lto( let work_product = green_modules[module_name].clone(); copy_jobs.push(work_product); info!(" - {}: re-used", module_name); - assert!(cgcx.incr_comp_session_dir.is_some()); continue; } } @@ -518,8 +529,8 @@ fn thin_lto( } // Save the current ThinLTO import information for the next compilation - // session, overwriting the previous serialized data (if any). - if let Some(path) = key_map_path + // session. + if let Some(path) = new_key_map_path && let Err(err) = curr_key_map.save_to_file(&path) { write::llvm_err(dcx, LlvmError::WriteThinLtoKey { err }); diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index 0f609464593dc..7633ae31af38f 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -15,7 +15,9 @@ use rustc_errors::{ Level, MultiSpan, Style, Sublevel, Suggestions, catch_fatal_errors, }; use rustc_fs_util::link_or_copy; -use rustc_incremental::{copy_cgu_workproduct_to_incr_comp_cache_dir, in_incr_comp_dir_sess}; +use rustc_incremental::{ + copy_cgu_workproduct_to_incr_comp_cache_dir, in_incr_comp_dir_sess, in_old_incr_comp_dir_sess, +}; use rustc_macros::{Decodable, Encodable}; use rustc_metadata::fs::copy_to_stdout; use rustc_middle::dep_graph::{WorkProduct, WorkProductMap}; @@ -351,9 +353,12 @@ pub struct CodegenContext { /// Directory into which should the LLVM optimization remarks be written. /// If `None`, they will be written to stderr. pub remark_dir: Option, + /// The previous incremental compilation session directory, or None if we + /// are not compiling incrementally or there is no previous session. + pub old_incr_comp_session_dir: Option, /// The incremental compilation session directory, or None if we are not /// compiling incrementally - pub incr_comp_session_dir: Option, + pub new_incr_comp_session_dir: Option, /// `Some(limit)` if the codegen should be run in parallel. /// /// Depends on [`WriteBackendMethods::supports_parallel()`] and `--jobs-backend`. @@ -497,7 +502,6 @@ fn copy_all_cgu_workproducts_to_incr_comp_cache_dir( incr_comp_session.unwrap(), &module.name, files.as_slice(), - &module.links_from_incr_cache, ); work_products.insert(id, product); } @@ -839,7 +843,7 @@ fn execute_optimize_work_item( // save our module to disk first. let bitcode = if cgcx.module_config.emit_pre_lto_bc { let filename = pre_lto_bitcode_filename(&module.name); - cgcx.incr_comp_session_dir.as_ref().map(|path| path.join(&filename)) + cgcx.new_incr_comp_session_dir.as_ref().map(|path| path.join(&filename)) } else { None }; @@ -886,11 +890,9 @@ fn execute_copy_from_cache_work_item( let dcx = DiagCtxt::new(Box::new(shared_emitter)); let dcx = dcx.handle(); - let incr_comp_session_dir = cgcx.incr_comp_session_dir.as_ref().unwrap(); - - let mut links_from_incr_cache = Vec::new(); + let incr_comp_session_dir = cgcx.old_incr_comp_session_dir.as_ref().unwrap(); - let mut load_from_incr_comp_dir = |output_path: PathBuf, saved_path: &str| { + let load_from_incr_comp_dir = |output_path: PathBuf, saved_path: &str| { let source_file_in_incr_comp_dir = incr_comp_session_dir.join(saved_path); debug!( "copying preexisting module `{}` from {:?} to {}", @@ -899,10 +901,7 @@ fn execute_copy_from_cache_work_item( output_path.display() ); match link_or_copy(&source_file_in_incr_comp_dir, &output_path) { - Ok(_) => { - links_from_incr_cache.push(source_file_in_incr_comp_dir); - Some(output_path) - } + Ok(_) => Some(output_path), Err(error) => { dcx.emit_err(diagnostics::CopyPathBuf { source_file: source_file_in_incr_comp_dir, @@ -925,7 +924,7 @@ fn execute_copy_from_cache_work_item( load_from_incr_comp_dir(dwarf_obj_out, saved_dwarf_object_file) }); - let mut load_from_incr_cache = |perform, output_type: OutputType| { + let load_from_incr_cache = |perform, output_type: OutputType| { if perform { let saved_file = module.source.saved_files.get(output_type.extension())?; let output_path = cgcx.output_filenames.temp_path_for_cgu(output_type, &module.name); @@ -953,7 +952,6 @@ fn execute_copy_from_cache_work_item( } CompiledModule { - links_from_incr_cache, kind: ModuleKind::Regular, name: module.name, object, @@ -1294,10 +1292,15 @@ fn start_executing_work( time_trace: sess.opts.unstable_opts.llvm_time_trace, remark: sess.opts.cg.remark.clone(), remark_dir, - incr_comp_session_dir: tcx + old_incr_comp_session_dir: tcx + .incr_comp_session + .as_ref() + .and_then(|incr_comp_session| incr_comp_session.old_session_directory.as_deref()) + .map(ToOwned::to_owned), + new_incr_comp_session_dir: tcx .incr_comp_session .as_ref() - .map(|incr_comp_session| (&*incr_comp_session.session_directory).to_owned()), + .map(|incr_comp_session| (&*incr_comp_session.new_session_directory).to_owned()), output_filenames: Arc::clone(tcx.output_filenames(())), module_config: regular_config, opt_level, @@ -2262,7 +2265,22 @@ pub(crate) fn submit_pre_lto_module_to_llvm( module: CachedModuleCodegen, ) { let filename = pre_lto_bitcode_filename(&module.name); + let old_bitcode_path = + in_old_incr_comp_dir_sess(tcx.incr_comp_session.unwrap(), &filename).unwrap(); let bitcode_path = in_incr_comp_dir_sess(tcx.incr_comp_session.unwrap(), &filename); + + match link_or_copy(&old_bitcode_path, &bitcode_path) { + Ok(_) => {} + Err(error) => { + tcx.sess.dcx().emit_err(diagnostics::CopyPathBuf { + source_file: old_bitcode_path, + output_path: bitcode_path, + error, + }); + return; + } + } + // Schedule the module to be loaded drop( coordinator diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs index 1272b26ca0612..5ae8ba4f7fded 100644 --- a/compiler/rustc_codegen_ssa/src/lib.rs +++ b/compiler/rustc_codegen_ssa/src/lib.rs @@ -114,7 +114,6 @@ impl ModuleCodegen { bytecode, assembly, llvm_ir, - links_from_incr_cache: Vec::new(), } } } @@ -129,7 +128,6 @@ pub struct CompiledModule { pub bytecode: Option, pub assembly: Option, // --emit=asm pub llvm_ir: Option, // --emit=llvm-ir, llvm-bc is in bytecode - pub links_from_incr_cache: Vec, } impl CompiledModule { diff --git a/compiler/rustc_incremental/src/diagnostics.rs b/compiler/rustc_incremental/src/diagnostics.rs index 6e291b7ea3abb..b9ac4662dcbdc 100644 --- a/compiler/rustc_incremental/src/diagnostics.rs +++ b/compiler/rustc_incremental/src/diagnostics.rs @@ -169,21 +169,6 @@ pub(crate) struct DeleteLock<'a> { pub err: std::io::Error, } -#[derive(Diagnostic)] -#[diag( - "hard linking files in the incremental compilation cache failed. copying files instead. consider moving the cache directory to a file system which supports hard linking in session dir `{$path}`" -)] -pub(crate) struct HardLinkFailed<'a> { - pub path: &'a Path, -} - -#[derive(Diagnostic)] -#[diag("failed to delete partly initialized session dir `{$path}`: {$err}")] -pub(crate) struct DeletePartial<'a> { - pub path: &'a Path, - pub err: std::io::Error, -} - #[derive(Diagnostic)] #[diag("did not finalize incremental compilation session directory `{$path}`: {$err}")] #[help("the next build will not be able to reuse work from this compilation")] @@ -266,13 +251,6 @@ pub(crate) struct CopyWorkProductToCache<'a> { pub err: std::io::Error, } -#[derive(Diagnostic)] -#[diag("file-system error deleting outdated file `{$path}`: {$err}")] -pub(crate) struct DeleteWorkProduct<'a> { - pub path: &'a Path, - pub err: std::io::Error, -} - #[derive(Diagnostic)] #[diag( "corrupt incremental compilation artifact found at `{$path}`. This file will automatically be ignored and deleted. If you see this message repeatedly or can provoke it without manually manipulating the compiler's artifacts, please file an issue. The incremental compilation system relies on hardlinks and filesystem locks behaving correctly, and may not deal well with OS crashes, so whatever information you can provide about your filesystem or other state may be very relevant" diff --git a/compiler/rustc_incremental/src/lib.rs b/compiler/rustc_incremental/src/lib.rs index 83646cb086d8d..b5470d224ffbd 100644 --- a/compiler/rustc_incremental/src/lib.rs +++ b/compiler/rustc_incremental/src/lib.rs @@ -3,6 +3,7 @@ // tidy-alphabetical-start #![deny(missing_docs)] #![feature(file_buffered)] +#![feature(try_blocks)] // tidy-alphabetical-end mod assert_dep_graph; @@ -11,7 +12,7 @@ mod persist; pub use persist::{ copy_cgu_workproduct_to_incr_comp_cache_dir, finalize_session_directory, in_incr_comp_dir_sess, - load_query_result_cache, save_work_product_index, setup_dep_graph, + in_old_incr_comp_dir_sess, load_query_result_cache, save_work_product_index, setup_dep_graph, }; use rustc_middle::util::Providers; diff --git a/compiler/rustc_incremental/src/persist/fs.rs b/compiler/rustc_incremental/src/persist/fs.rs index 374e0b0dd6d1a..0a828b4d5967a 100644 --- a/compiler/rustc_incremental/src/persist/fs.rs +++ b/compiler/rustc_incremental/src/persist/fs.rs @@ -110,11 +110,11 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; use rand::{RngCore, rng}; use rustc_data_structures::base_n::{BaseNString, CASE_INSENSITIVE, ToBaseN}; -use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; +use rustc_data_structures::fx::FxIndexSet; use rustc_data_structures::svh::Svh; use rustc_data_structures::unord::{UnordMap, UnordSet}; use rustc_data_structures::{base_n, flock}; -use rustc_fs_util::{LinkOrCopy, link_or_copy, try_canonicalize}; +use rustc_fs_util::try_canonicalize; use rustc_middle::dep_graph::WorkProduct; use rustc_session::config::OutputType; use rustc_session::{IncrCompSession, Session, StableCrateId}; @@ -138,6 +138,11 @@ const QUERY_CACHE_FILENAME: &str = "query-cache.bin"; // case-sensitive (as opposed to base64, for example). const INT_ENCODE_BASE: usize = base_n::CASE_INSENSITIVE; +/// Returns the path to a previous session's dependency graph. +pub(crate) fn old_dep_graph_path(incr_comp_session: &IncrCompSession) -> Option { + in_old_incr_comp_dir_sess(incr_comp_session, DEP_GRAPH_FILENAME) +} + /// Returns the path to a session's dependency graph. pub(crate) fn dep_graph_path(incr_comp_session: &IncrCompSession) -> PathBuf { in_incr_comp_dir_sess(incr_comp_session, DEP_GRAPH_FILENAME) @@ -151,10 +156,19 @@ pub(crate) fn staging_dep_graph_path(incr_comp_session: &IncrCompSession) -> Pat in_incr_comp_dir_sess(incr_comp_session, STAGING_DEP_GRAPH_FILENAME) } +pub(crate) fn old_work_products_path(incr_comp_session: &IncrCompSession) -> Option { + in_old_incr_comp_dir_sess(incr_comp_session, WORK_PRODUCTS_FILENAME) +} + pub(crate) fn work_products_path(incr_comp_session: &IncrCompSession) -> PathBuf { in_incr_comp_dir_sess(incr_comp_session, WORK_PRODUCTS_FILENAME) } +/// Returns the path to a previous session's query cache. +pub(crate) fn old_query_cache_path(incr_comp_session: &IncrCompSession) -> Option { + in_old_incr_comp_dir_sess(incr_comp_session, QUERY_CACHE_FILENAME) +} + /// Returns the path to a session's query cache. pub(crate) fn query_cache_path(incr_comp_session: &IncrCompSession) -> PathBuf { in_incr_comp_dir_sess(incr_comp_session, QUERY_CACHE_FILENAME) @@ -182,10 +196,19 @@ fn lock_file_path(session_dir: &Path) -> PathBuf { crate_dir.join(&directory_name[0..dash_indices[2]]).with_extension(&LOCK_FILE_EXT[1..]) } +/// Returns the path for a given filename within the incremental compilation directory +/// in the previous session. +pub fn in_old_incr_comp_dir_sess( + incr_comp_session: &IncrCompSession, + file_name: &str, +) -> Option { + incr_comp_session.old_session_directory.as_ref().map(|dir| dir.join(file_name)) +} + /// Returns the path for a given filename within the incremental compilation directory /// in the current session. pub fn in_incr_comp_dir_sess(incr_comp_session: &IncrCompSession, file_name: &str) -> PathBuf { - incr_comp_session.session_directory.join(file_name) + incr_comp_session.new_session_directory.join(file_name) } /// Allocates the private session directory. @@ -230,62 +253,30 @@ pub(crate) fn prepare_session_directory( } }; - let mut source_directories_already_tried = FxHashSet::default(); - - loop { - // Generate a session directory of the form: - // - // {incr-comp-dir}/{crate-name-and-disambiguator}/s-{timestamp}-{random}-working - let session_directory = generate_session_dir_path(&crate_dir); - debug!("session-dir: {}", session_directory.display()); - - // Lock the new session directory. If this fails, return an - // error without retrying - let (session_directory, lock_file_path) = - lock_and_create_directory(sess, &session_directory); - - // Find a suitable source directory to copy from. Ignore those that we - // have already tried before. - let source_directory = find_source_directory(&crate_dir, &source_directories_already_tried); - - let Some(source_directory) = source_directory else { - // There's nowhere to copy from, we're done - debug!( - "no source directory found. Continuing with empty session \ - directory." - ); - - return IncrCompSession { session_directory }; - }; - - debug!("attempting to copy data from source: {}", source_directory.display()); - - // Try copying over all files from the source directory - if let Ok(allows_links) = copy_files(sess, &session_directory, &source_directory) { - debug!("successfully copied data from: {}", source_directory.display()); + // Generate a session directory of the form: + // + // {incr-comp-dir}/{crate-name-and-disambiguator}/s-{timestamp}-{random}-working + let new_session_dir = generate_session_dir_path(&crate_dir); + debug!("session-dir: {}", new_session_dir.display()); - if !allows_links { - sess.dcx().emit_warn(diagnostics::HardLinkFailed { path: &session_directory }); - } - - return IncrCompSession { session_directory }; - } else { - debug!("copying failed - trying next directory"); + // Lock the new session directory. If this fails, return an + // error without retrying + let new_session_directory = lock_directory(sess, &new_session_dir, true, true) + .expect("should emit fatal error on lock fail"); - // Something went wrong while trying to copy/link files from the - // source directory. Try again with a different one. - source_directories_already_tried.insert(source_directory); + // Find a suitable source directory to copy from. Ignore those that we + // have already tried before. + let old_source_directory = find_source_directory(sess, &crate_dir); - // Try to remove the session directory we just allocated. We don't - // know if there's any garbage in it from the failed copy action. - if let Err(err) = std_fs::remove_dir_all(&*session_directory) { - sess.dcx().emit_warn(diagnostics::DeletePartial { path: &session_directory, err }); - } + let old_session_directory = if let Some(old_source_directory) = old_source_directory { + debug!("attempting to use: {}", old_source_directory.display()); + Some(old_source_directory) + } else { + debug!("no source directory found. Continuing with empty session directory."); + None + }; - delete_session_dir_lock_file(sess, &lock_file_path); - drop(session_directory); - } - } + return IncrCompSession { old_session_directory, new_session_directory }; } /// This function finalizes and thus 'publishes' the session directory by @@ -301,13 +292,13 @@ pub fn finalize_session_directory( if sess.opts.incremental.is_none() { return; } - let incr_comp_session = incr_comp_session.unwrap(); + let mut incr_comp_session = incr_comp_session.unwrap(); // The svh is always produced when incr. comp. is enabled. let svh = svh.unwrap(); let _timer = sess.timer("incr_comp_finalize_session_directory"); - let incr_comp_session_dir = &*incr_comp_session.session_directory; + let incr_comp_session_dir = &*incr_comp_session.new_session_directory; debug!("finalize_session_directory() - session directory: {}", incr_comp_session_dir.display()); @@ -367,77 +358,28 @@ pub fn finalize_session_directory( } } - let _ = garbage_collect_session_directories( - sess, - &incr_comp_session, - false, // keep_most_recent - ); -} + // Unlock the old session directory now that we will no longer read from it. + incr_comp_session.old_session_directory = None; -pub(crate) fn delete_all_session_dir_contents( - incr_comp_session: &IncrCompSession, -) -> io::Result<()> { - let sess_dir_iterator = incr_comp_session.session_directory.read_dir()?; - for entry in sess_dir_iterator { - let entry = entry?; - safe_remove_file(&entry.path())? - } - Ok(()) + let _ = garbage_collect_session_directories(sess, &incr_comp_session); } -fn copy_files(sess: &Session, target_dir: &Path, source_dir: &Path) -> Result { - // We acquire a shared lock on the lock file of the directory, so that - // nobody deletes it out from under us while we are reading from it. - let lock_file_path = lock_file_path(source_dir); - - // not exclusive - let Ok(_lock) = flock::Lock::try_lock( - &lock_file_path, - false, // don't create - false, - ) else { - // Could not acquire the lock, don't try to copy from here - return Err(()); - }; - - let Ok(source_dir_iterator) = source_dir.read_dir() else { - return Err(()); - }; - - let mut files_linked = 0; - let mut files_copied = 0; - - for entry in source_dir_iterator { - match entry { - Ok(entry) => { - let file_name = entry.file_name(); - - let target_file_path = target_dir.join(file_name); - let source_path = entry.path(); - - debug!("copying into session dir: {}", source_path.display()); - match link_or_copy(source_path, target_file_path) { - Ok(LinkOrCopy::Link) => files_linked += 1, - Ok(LinkOrCopy::Copy) => files_copied += 1, - Err(_) => return Err(()), - } +pub(crate) fn invalidate_old_session_dir(sess: &Session, incr_comp_session: &mut IncrCompSession) { + if let Some(old_incr_comp_session_dir) = incr_comp_session.old_session_directory.take() { + let res = try { + let sess_dir_iterator = old_incr_comp_session_dir.read_dir()?; + for entry in sess_dir_iterator { + let entry = entry?; + safe_remove_file(&entry.path())? } - Err(_) => return Err(()), + }; + if let Err(err) = res { + sess.dcx().emit_err(diagnostics::DeleteIncompatible { + path: (*old_incr_comp_session_dir).to_owned(), + err, + }); } } - - if sess.opts.unstable_opts.incremental_info { - eprintln!( - "[incremental] session directory: \ - {files_linked} files hard-linked" - ); - eprintln!( - "[incremental] session directory: \ - {files_copied} files copied" - ); - } - - Ok(files_linked > 0 || files_copied == 0) } /// Generates unique directory path of the form: @@ -471,7 +413,12 @@ fn create_dir(sess: &Session, path: &Path, dir_tag: &str) { } /// Allocate the lock-file, lock it and create the session directory. -fn lock_and_create_directory(sess: &Session, session_dir: &Path) -> (flock::LockedDir, PathBuf) { +fn lock_directory( + sess: &Session, + session_dir: &Path, + create: bool, + fatal: bool, +) -> Option { let lock_file_path = lock_file_path(session_dir); debug!("lock_directory() - lock_file: {}", lock_file_path.display()); @@ -485,18 +432,26 @@ fn lock_and_create_directory(sess: &Session, session_dir: &Path) -> (flock::Lock Ok(lock) => { // Now that we have the lock, we can actually create the session // directory - create_dir(sess, &session_dir, "session"); + if create { + create_dir(sess, &session_dir, "session"); + } - (lock, lock_file_path) + Some(lock) } Err(lock_err) => { let is_unsupported_lock = flock::Lock::error_unsupported(&lock_err); - sess.dcx().emit_fatal(diagnostics::CreateLock { + let diag = diagnostics::CreateLock { lock_err, session_dir, is_unsupported_lock, is_cargo: rustc_session::utils::was_invoked_from_cargo(), - }); + }; + if fatal { + sess.dcx().emit_fatal(diag); + } else { + sess.dcx().emit_warn(diag); + None + } } } } @@ -507,24 +462,18 @@ fn delete_session_dir_lock_file(sess: &Session, lock_file_path: &Path) { } } -/// Finds the most recent published session directory that is not in the -/// ignore-list. -fn find_source_directory( - crate_dir: &Path, - source_directories_already_tried: &FxHashSet, -) -> Option { +/// Finds the most recent published session directory. +fn find_source_directory(sess: &Session, crate_dir: &Path) -> Option { let iter = crate_dir .read_dir() .unwrap() // FIXME .filter_map(|e| e.ok().map(|e| e.path())); - find_source_directory_in_iter(iter, source_directories_already_tried) + find_source_directory_in_iter(iter) + .and_then(|session_dir| lock_directory(sess, &session_dir, false, false)) } -fn find_source_directory_in_iter( - iter: I, - source_directories_already_tried: &FxHashSet, -) -> Option +fn find_source_directory_in_iter(iter: I) -> Option where I: Iterator, { @@ -538,10 +487,7 @@ where continue; }; - if source_directories_already_tried.contains(&session_dir) - || !is_session_directory(&directory_name) - || !is_finalized(&directory_name) - { + if !is_session_directory(&directory_name) || !is_finalized(&directory_name) { debug!("find_source_directory_in_iter - ignoring"); continue; } @@ -619,11 +565,10 @@ fn is_old_enough_to_be_collected(timestamp: SystemTime) -> bool { pub(crate) fn garbage_collect_session_directories( sess: &Session, incr_comp_session: &IncrCompSession, - keep_most_recent: bool, ) -> io::Result<()> { debug!("garbage_collect_session_directories() - begin"); - let session_directory = &*incr_comp_session.session_directory; + let session_directory = &*incr_comp_session.new_session_directory; debug!( "garbage_collect_session_directories() - session directory: {}", @@ -760,10 +705,7 @@ pub(crate) fn garbage_collect_session_directories( ); // Note that we are holding on to the lock - return Some(( - (timestamp, crate_directory.join(directory_name)), - Some(lock), - )); + return Some((crate_directory.join(directory_name), lock)); } Err(_) => { debug!( @@ -818,25 +760,22 @@ pub(crate) fn garbage_collect_session_directories( } None }); - let deletion_candidates = deletion_candidates.into(); // Delete all but the most recent of the candidates - all_except_maybe_most_recent(deletion_candidates, keep_most_recent).into_items().all( - |(path, lock)| { - debug!("garbage_collect_session_directories() - deleting `{}`", path.display()); + deletion_candidates.all(|(path, lock)| { + debug!("garbage_collect_session_directories() - deleting `{}`", path.display()); - if let Err(err) = std_fs::remove_dir_all(&path) { - sess.dcx().emit_warn(diagnostics::FinalizedGcFailed { path: &path, err }); - } else { - delete_session_dir_lock_file(sess, &lock_file_path(&path)); - } + if let Err(err) = std_fs::remove_dir_all(&path) { + sess.dcx().emit_warn(diagnostics::FinalizedGcFailed { path: &path, err }); + } else { + delete_session_dir_lock_file(sess, &lock_file_path(&path)); + } - // Let's make it explicit that the file lock is released at this point, - // or rather, that we held on to it until here - drop(lock); - true - }, - ); + // Let's make it explicit that the file lock is released at this point, + // or rather, that we held on to it until here + drop(lock); + true + }); Ok(()) } @@ -851,21 +790,6 @@ fn delete_old(sess: &Session, path: &Path) { } } -fn all_except_maybe_most_recent( - deletion_candidates: UnordMap<(SystemTime, PathBuf), Option>, - keep_most_recent: bool, -) -> UnordMap> { - let most_recent = keep_most_recent - .then(|| deletion_candidates.items().map(|(&(timestamp, _), _)| timestamp).max()) - .flatten(); - - deletion_candidates - .into_items() - .filter(|&((timestamp, _), _)| Some(timestamp) != most_recent) - .map(|((_, path), lock)| (path, lock)) - .collect() -} - fn safe_remove_file(p: &Path) -> io::Result<()> { match std_fs::remove_file(p) { Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(()), diff --git a/compiler/rustc_incremental/src/persist/fs/tests.rs b/compiler/rustc_incremental/src/persist/fs/tests.rs index 75b573dd4944a..112afd8c7bd1d 100644 --- a/compiler/rustc_incremental/src/persist/fs/tests.rs +++ b/compiler/rustc_incremental/src/persist/fs/tests.rs @@ -1,25 +1,5 @@ use super::*; -#[test] -fn test_all_except_most_recent() { - let input: UnordMap<_, Option> = UnordMap::from_iter([ - ((UNIX_EPOCH + Duration::new(4, 0), PathBuf::from("4")), None), - ((UNIX_EPOCH + Duration::new(1, 0), PathBuf::from("1")), None), - ((UNIX_EPOCH + Duration::new(5, 0), PathBuf::from("5")), None), - ((UNIX_EPOCH + Duration::new(3, 0), PathBuf::from("3")), None), - ((UNIX_EPOCH + Duration::new(2, 0), PathBuf::from("2")), None), - ]); - assert_eq!( - all_except_maybe_most_recent(input, true) - .into_items() - .map(|(path, _)| path) - .into_sorted_stable_ord(), - vec![PathBuf::from("1"), PathBuf::from("2"), PathBuf::from("3"), PathBuf::from("4")] - ); - - assert!(all_except_maybe_most_recent(UnordMap::default(), true).is_empty()); -} - #[test] fn test_timestamp_serialization() { for i in 0..1_000u64 { @@ -31,8 +11,6 @@ fn test_timestamp_serialization() { #[test] fn test_find_source_directory_in_iter() { - let already_visited = FxHashSet::default(); - // Find newest assert_eq!( find_source_directory_in_iter( @@ -42,7 +20,6 @@ fn test_find_source_directory_in_iter() { PathBuf::from("crate-dir/s-1234-0000-svh") ] .into_iter(), - &already_visited ), Some(PathBuf::from("crate-dir/s-3234-0000-svh")) ); @@ -56,13 +33,12 @@ fn test_find_source_directory_in_iter() { PathBuf::from("crate-dir/s-1234-0000-svh") ] .into_iter(), - &already_visited ), Some(PathBuf::from("crate-dir/s-2234-0000-svh")) ); // Handle empty - assert_eq!(find_source_directory_in_iter([].into_iter(), &already_visited), None); + assert_eq!(find_source_directory_in_iter([].into_iter()), None); // Handle only working assert_eq!( @@ -73,7 +49,6 @@ fn test_find_source_directory_in_iter() { PathBuf::from("crate-dir/s-1234-0000-working") ] .into_iter(), - &already_visited ), None ); diff --git a/compiler/rustc_incremental/src/persist/load.rs b/compiler/rustc_incremental/src/persist/load.rs index fb83149e65b22..76e0bf92c6f0f 100644 --- a/compiler/rustc_incremental/src/persist/load.rs +++ b/compiler/rustc_incremental/src/persist/load.rs @@ -16,8 +16,8 @@ use rustc_span::Symbol; use tracing::{debug, warn}; use super::data::*; +use super::file_format; use super::fs::*; -use super::{file_format, work_product}; use crate::diagnostics; use crate::persist::file_format::{OpenFile, OpenFileError}; @@ -32,15 +32,6 @@ enum LoadResult { IoError { path: PathBuf, err: io::Error }, } -fn delete_dirty_work_product( - sess: &Session, - incr_comp_session: &IncrCompSession, - swp: SerializedWorkProduct, -) { - debug!("delete_dirty_work_product({:?})", swp); - work_product::delete_workproduct_files(sess, incr_comp_session, &swp.work_product); -} - fn load_dep_graph(sess: &Session, incr_comp_session: &IncrCompSession) -> LoadResult { assert!(sess.opts.incremental.is_some()); @@ -48,12 +39,16 @@ fn load_dep_graph(sess: &Session, incr_comp_session: &IncrCompSession) -> LoadRe // 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(incr_comp_session); + let Some(path) = old_dep_graph_path(incr_comp_session) else { + return LoadResult::DataOutOfDate; + }; let expected_hash = sess.opts.dep_tracking_hash(false); let mut prev_work_products = UnordMap::default(); - let work_products_path = work_products_path(incr_comp_session); + let Some(work_products_path) = old_work_products_path(incr_comp_session) else { + return LoadResult::DataOutOfDate; + }; if let Ok(OpenFile { mmap, start_pos }) = file_format::open_incremental_file(sess, &work_products_path) @@ -68,7 +63,7 @@ fn load_dep_graph(sess: &Session, incr_comp_session: &IncrCompSession) -> LoadRe for swp in work_products { let all_files_exist = swp.work_product.saved_files.items().all(|(_, path)| { - let exists = in_incr_comp_dir_sess(incr_comp_session, path).exists(); + let exists = in_old_incr_comp_dir_sess(incr_comp_session, path).unwrap().exists(); if !exists && sess.opts.unstable_opts.incremental_info { eprintln!("incremental: could not find file for work product: {path}",); } @@ -80,7 +75,7 @@ fn load_dep_graph(sess: &Session, incr_comp_session: &IncrCompSession) -> LoadRe 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, incr_comp_session, swp); + return LoadResult::DataOutOfDate; } } } @@ -134,7 +129,9 @@ pub fn load_query_result_cache( let _prof_timer = sess.prof.generic_activity("incr_comp_load_query_result_cache"); - let path = query_cache_path(incr_comp_session); + let Some(path) = old_query_cache_path(incr_comp_session) else { + return Some(OnDiskCache::new_empty()); + }; match file_format::open_incremental_file(sess, &path) { Ok(OpenFile { mmap, start_pos }) => { let cache = OnDiskCache::new(sess, mmap, start_pos).unwrap_or_else(|()| { @@ -190,16 +187,12 @@ pub fn setup_dep_graph( } // `load_dep_graph` can only be called after `prepare_session_directory`. - let incr_comp_session = prepare_session_directory(sess, crate_name, stable_crate_id); + let mut incr_comp_session = 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, &incr_comp_session); sess.time("incr_comp_garbage_collect_session_directories", || { - if let Err(e) = garbage_collect_session_directories( - sess, - &incr_comp_session, - true, // keep_most_recent - ) { + if let Err(e) = garbage_collect_session_directories(sess, &incr_comp_session) { warn!( "Error while trying to garbage collect incremental compilation \ cache directory: {e}", @@ -213,15 +206,11 @@ pub fn setup_dep_graph( let (prev_graph, prev_work_products) = match load_result { LoadResult::IoError { path, err } => { sess.dcx().emit_warn(diagnostics::LoadDepGraph { path, err }); + invalidate_old_session_dir(sess, &mut incr_comp_session); Default::default() } LoadResult::DataOutOfDate => { - if let Err(err) = delete_all_session_dir_contents(&incr_comp_session) { - sess.dcx().emit_err(diagnostics::DeleteIncompatible { - path: dep_graph_path(&incr_comp_session), - err, - }); - } + invalidate_old_session_dir(sess, &mut incr_comp_session); Default::default() } LoadResult::Ok { prev_graph, prev_work_products } => (prev_graph, prev_work_products), diff --git a/compiler/rustc_incremental/src/persist/mod.rs b/compiler/rustc_incremental/src/persist/mod.rs index 7d486cc394b80..fb318357b26cb 100644 --- a/compiler/rustc_incremental/src/persist/mod.rs +++ b/compiler/rustc_incremental/src/persist/mod.rs @@ -10,7 +10,7 @@ mod load; mod save; mod work_product; -pub use fs::{finalize_session_directory, in_incr_comp_dir_sess}; +pub use fs::{finalize_session_directory, in_incr_comp_dir_sess, in_old_incr_comp_dir_sess}; pub use load::{load_query_result_cache, setup_dep_graph}; pub(crate) use save::save_dep_graph; pub use save::save_work_product_index; diff --git a/compiler/rustc_incremental/src/persist/save.rs b/compiler/rustc_incremental/src/persist/save.rs index 12f674fe2a859..46f47d6c8623c 100644 --- a/compiler/rustc_incremental/src/persist/save.rs +++ b/compiler/rustc_incremental/src/persist/save.rs @@ -11,7 +11,7 @@ use tracing::debug; use super::data::*; use super::fs::*; -use super::{clean, file_format, work_product}; +use super::{clean, file_format}; use crate::assert_dep_graph::assert_dep_graph; use crate::diagnostics; @@ -112,23 +112,6 @@ pub fn save_work_product_index( e.finish() }); - // We also need to clean out old work-products, as not all of them are - // deleted during invalidation. Some object files don't change their - // content, they are just not needed anymore. - let previous_work_products = dep_graph.previous_work_products(); - for (id, wp) in previous_work_products.to_sorted_stable_ord() { - if !new_work_products.contains_key(id) { - work_product::delete_workproduct_files(sess, incr_comp_session.unwrap(), wp); - debug_assert!( - !wp.saved_files.items().all(|(_, path)| in_incr_comp_dir_sess( - incr_comp_session.unwrap(), - path - ) - .exists()) - ); - } - } - // Check that we did not delete one of the current work-products: debug_assert!({ new_work_products.items().all(|(_, wp)| { diff --git a/compiler/rustc_incremental/src/persist/work_product.rs b/compiler/rustc_incremental/src/persist/work_product.rs index 7bb66fee4d1a3..0aaa9aa8e96c2 100644 --- a/compiler/rustc_incremental/src/persist/work_product.rs +++ b/compiler/rustc_incremental/src/persist/work_product.rs @@ -1,9 +1,8 @@ -//! Functions for saving and removing intermediate [work products]. +//! Function for saving intermediate [work products]. //! //! [work products]: WorkProduct -use std::fs as std_fs; -use std::path::{Path, PathBuf}; +use std::path::Path; use rustc_data_structures::unord::UnordMap; use rustc_fs_util::link_or_copy; @@ -23,7 +22,6 @@ pub fn copy_cgu_workproduct_to_incr_comp_cache_dir( incr_comp_session: &IncrCompSession, cgu_name: &str, files: &[(&'static str, &Path)], - known_links: &[PathBuf], ) -> (WorkProductId, WorkProduct) { debug!(?cgu_name, ?files); assert!(sess.opts.incremental.is_some()); @@ -32,10 +30,6 @@ pub fn copy_cgu_workproduct_to_incr_comp_cache_dir( for (ext, path) in files { let file_name = format!("{cgu_name}.{ext}"); let path_in_incr_dir = in_incr_comp_dir_sess(incr_comp_session, &file_name); - if known_links.contains(&path_in_incr_dir) { - let _ = saved_files.insert(ext.to_string(), file_name); - continue; - } match link_or_copy(path, &path_in_incr_dir) { Ok(_) => { let _ = saved_files.insert(ext.to_string(), file_name); @@ -55,17 +49,3 @@ pub fn copy_cgu_workproduct_to_incr_comp_cache_dir( let work_product_id = WorkProductId::from_cgu_name(cgu_name); (work_product_id, work_product) } - -/// Removes files for a given work product. -pub(crate) fn delete_workproduct_files( - sess: &Session, - incr_comp_session: &IncrCompSession, - work_product: &WorkProduct, -) { - for (_, path) in work_product.saved_files.items().into_sorted_stable_ord() { - let path = in_incr_comp_dir_sess(incr_comp_session, path); - if let Err(err) = std_fs::remove_file(&path) { - sess.dcx().emit_warn(diagnostics::DeleteWorkProduct { path: &path, err }); - } - } -} diff --git a/compiler/rustc_interface/src/queries.rs b/compiler/rustc_interface/src/queries.rs index 2f196c5e5d609..759297bc69592 100644 --- a/compiler/rustc_interface/src/queries.rs +++ b/compiler/rustc_interface/src/queries.rs @@ -101,7 +101,6 @@ impl Linker { incr_comp_session.as_ref().unwrap(), WorkProduct::METADATA_WORKPRODUCT_CGU_NAME, &[(OutputType::Metadata.extension(), path)], - &[], ); work_products.insert(id, product); } diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 8481db9d3c523..f64b2913d40d9 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -2502,14 +2502,15 @@ pub fn encode_metadata(tcx: TyCtxt<'_>, path: &Path, ref_path: Option<&Path>) { // If the metadata dep-node is green, try to reuse the saved work product. if tcx.dep_graph.is_fully_enabled() + && let incr_comp_session = tcx.incr_comp_session.unwrap() + && let Some(old_incr_comp_session_dir) = &incr_comp_session.old_session_directory && let work_product_id = WorkProductId::from_cgu_name(WorkProduct::METADATA_WORKPRODUCT_CGU_NAME) && let Some(work_product) = tcx.dep_graph.previous_work_product(&work_product_id) && tcx.dep_graph.try_mark_green(tcx, &dep_node).is_some() { let saved_path = &work_product.saved_files[OutputType::Metadata.extension()]; - let incr_comp_session_dir = &tcx.incr_comp_session.unwrap().session_directory; - let source_file_in_incr_dir = &incr_comp_session_dir.join(saved_path); + let source_file_in_incr_dir = &old_incr_comp_session_dir.join(saved_path); debug!("copying preexisting metadata from {source_file_in_incr_dir:?} to {path:?}"); match rustc_fs_util::link_or_copy(&source_file_in_incr_dir, path) { Ok(_) => {} diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index acfbb08b9b630..0ebf5dd1b99a1 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -1841,10 +1841,11 @@ fn validate_commandline_args_with_session_available(sess: &Session) { /// Holds data on the current incremental compilation session, if there is one. pub struct IncrCompSession { - /// The directory containing all cached data. Cached data from a previous - /// session can be read out of it and new data for the current session will - /// be written into it. - pub session_directory: flock::LockedDir, + /// The directory from which cached data of a previous session can be read. + pub old_session_directory: Option, + /// The directory to which cached data for the current session can be + /// written to. + pub new_session_directory: flock::LockedDir, } /// A wrapper around an [`DiagCtxt`] that is used for early error emissions. diff --git a/tests/run-make/incremental-session-gc/rmake.rs b/tests/run-make/incremental-session-gc/rmake.rs index 341a21690f215..405708abb7e3c 100644 --- a/tests/run-make/incremental-session-gc/rmake.rs +++ b/tests/run-make/incremental-session-gc/rmake.rs @@ -12,14 +12,12 @@ fn main() { compile(); let mut previous = session_dir(); - rfs::write(previous.join("sentinel"), "previous session"); for _ in 0..2 { compile(); let current = session_dir(); assert_ne!(previous, current); assert!(!previous.exists(), "superseded session was not collected: {previous:?}"); - assert_eq!(rfs::read_to_string(current.join("sentinel")), "previous session"); previous = current; } @@ -36,7 +34,6 @@ fn main() { let current = session_dir(); assert_ne!(previous, newer); assert!(!newer.exists(), "superseded session was not collected: {previous:?}"); - assert_eq!(rfs::read_to_string(current.join("sentinel")), "previous session"); } fn session_dir() -> PathBuf {