about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMichael Goulet <michael@errs.io>2025-04-06 23:50:16 +0000
committerMichael Goulet <michael@errs.io>2025-04-07 20:48:40 +0000
commit9c372d8940a8dad14d586f6bd4dd42541f25cd80 (patch)
treef5f7f7735e27aa5e524b4f815fa63c5f836233bf
parenteffef88ac70f2d12229b77b8e428037df8028b7e (diff)
downloadrust-9c372d8940a8dad14d586f6bd4dd42541f25cd80.tar.gz
rust-9c372d8940a8dad14d586f6bd4dd42541f25cd80.zip
Prepend temp files with a string per invocation of rustc
-rw-r--r--Cargo.lock1
-rw-r--r--compiler/rustc_codegen_cranelift/src/driver/aot.rs35
-rw-r--r--compiler/rustc_codegen_cranelift/src/global_asm.rs3
-rw-r--r--compiler/rustc_codegen_cranelift/src/lib.rs2
-rw-r--r--compiler/rustc_codegen_gcc/src/back/write.rs26
-rw-r--r--compiler/rustc_codegen_llvm/src/back/write.rs57
-rw-r--r--compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs1
-rw-r--r--compiler/rustc_codegen_ssa/src/back/link.rs7
-rw-r--r--compiler/rustc_codegen_ssa/src/back/write.rs29
-rw-r--r--compiler/rustc_codegen_ssa/src/base.rs8
-rw-r--r--compiler/rustc_codegen_ssa/src/lib.rs19
-rw-r--r--compiler/rustc_interface/src/passes.rs1
-rw-r--r--compiler/rustc_session/Cargo.toml1
-rw-r--r--compiler/rustc_session/src/config.rs40
-rw-r--r--compiler/rustc_session/src/session.rs17
-rw-r--r--tests/run-make/dirty-incr-due-to-hard-link/rmake.rs32
-rw-r--r--tests/run-make/dirty-incr-due-to-hard-link/test.rs31
17 files changed, 260 insertions, 50 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 52c9269fa87..4654b99960f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4407,6 +4407,7 @@ dependencies = [
  "bitflags",
  "getopts",
  "libc",
+ "rand 0.9.0",
  "rustc_abi",
  "rustc_ast",
  "rustc_data_structures",
diff --git a/compiler/rustc_codegen_cranelift/src/driver/aot.rs b/compiler/rustc_codegen_cranelift/src/driver/aot.rs
index 4ac7b86f085..0ea0dbfc44b 100644
--- a/compiler/rustc_codegen_cranelift/src/driver/aot.rs
+++ b/compiler/rustc_codegen_cranelift/src/driver/aot.rs
@@ -169,8 +169,11 @@ fn produce_final_output_artifacts(
         if codegen_results.modules.len() == 1 {
             // 1) Only one codegen unit. In this case it's no difficulty
             //    to copy `foo.0.x` to `foo.x`.
-            let path =
-                crate_output.temp_path_for_cgu(output_type, &codegen_results.modules[0].name);
+            let path = crate_output.temp_path_for_cgu(
+                output_type,
+                &codegen_results.modules[0].name,
+                sess.invocation_temp.as_deref(),
+            );
             let output = crate_output.path(output_type);
             if !output_type.is_text_output() && output.is_tty() {
                 sess.dcx()
@@ -345,6 +348,7 @@ fn make_module(sess: &Session, name: String) -> UnwindModule<ObjectModule> {
 
 fn emit_cgu(
     output_filenames: &OutputFilenames,
+    invocation_temp: Option<&str>,
     prof: &SelfProfilerRef,
     name: String,
     module: UnwindModule<ObjectModule>,
@@ -360,6 +364,7 @@ fn emit_cgu(
 
     let module_regular = emit_module(
         output_filenames,
+        invocation_temp,
         prof,
         product.object,
         ModuleKind::Regular,
@@ -385,6 +390,7 @@ fn emit_cgu(
 
 fn emit_module(
     output_filenames: &OutputFilenames,
+    invocation_temp: Option<&str>,
     prof: &SelfProfilerRef,
     mut object: cranelift_object::object::write::Object<'_>,
     kind: ModuleKind,
@@ -403,7 +409,7 @@ fn emit_module(
         object.set_section_data(comment_section, producer, 1);
     }
 
-    let tmp_file = output_filenames.temp_path_for_cgu(OutputType::Object, &name);
+    let tmp_file = output_filenames.temp_path_for_cgu(OutputType::Object, &name, invocation_temp);
     let file = match File::create(&tmp_file) {
         Ok(file) => file,
         Err(err) => return Err(format!("error creating object file: {}", err)),
@@ -443,8 +449,11 @@ fn reuse_workproduct_for_cgu(
     cgu: &CodegenUnit<'_>,
 ) -> Result<ModuleCodegenResult, String> {
     let work_product = cgu.previous_work_product(tcx);
-    let obj_out_regular =
-        tcx.output_filenames(()).temp_path_for_cgu(OutputType::Object, cgu.name().as_str());
+    let obj_out_regular = tcx.output_filenames(()).temp_path_for_cgu(
+        OutputType::Object,
+        cgu.name().as_str(),
+        tcx.sess.invocation_temp.as_deref(),
+    );
     let source_file_regular = rustc_incremental::in_incr_comp_dir_sess(
         &tcx.sess,
         &work_product.saved_files.get("o").expect("no saved object file in work product"),
@@ -589,13 +598,19 @@ fn module_codegen(
 
         let global_asm_object_file =
             profiler.generic_activity_with_arg("compile assembly", &*cgu_name).run(|| {
-                crate::global_asm::compile_global_asm(&global_asm_config, &cgu_name, &cx.global_asm)
+                crate::global_asm::compile_global_asm(
+                    &global_asm_config,
+                    &cgu_name,
+                    &cx.global_asm,
+                    cx.invocation_temp.as_deref(),
+                )
             })?;
 
         let codegen_result =
             profiler.generic_activity_with_arg("write object file", &*cgu_name).run(|| {
                 emit_cgu(
                     &global_asm_config.output_filenames,
+                    cx.invocation_temp.as_deref(),
                     &profiler,
                     cgu_name,
                     module,
@@ -620,8 +635,11 @@ fn emit_metadata_module(tcx: TyCtxt<'_>, metadata: &EncodedMetadata) -> Compiled
         .as_str()
         .to_string();
 
-    let tmp_file =
-        tcx.output_filenames(()).temp_path_for_cgu(OutputType::Metadata, &metadata_cgu_name);
+    let tmp_file = tcx.output_filenames(()).temp_path_for_cgu(
+        OutputType::Metadata,
+        &metadata_cgu_name,
+        tcx.sess.invocation_temp.as_deref(),
+    );
 
     let symbol_name = rustc_middle::middle::exported_symbols::metadata_symbol_name(tcx);
     let obj = create_compressed_metadata_file(tcx.sess, metadata, &symbol_name);
@@ -651,6 +669,7 @@ fn emit_allocator_module(tcx: TyCtxt<'_>) -> Option<CompiledModule> {
 
         match emit_module(
             tcx.output_filenames(()),
+            tcx.sess.invocation_temp.as_deref(),
             &tcx.sess.prof,
             product.object,
             ModuleKind::Allocator,
diff --git a/compiler/rustc_codegen_cranelift/src/global_asm.rs b/compiler/rustc_codegen_cranelift/src/global_asm.rs
index 6e3b3677a7f..79cefb05de3 100644
--- a/compiler/rustc_codegen_cranelift/src/global_asm.rs
+++ b/compiler/rustc_codegen_cranelift/src/global_asm.rs
@@ -132,6 +132,7 @@ pub(crate) fn compile_global_asm(
     config: &GlobalAsmConfig,
     cgu_name: &str,
     global_asm: &str,
+    invocation_temp: Option<&str>,
 ) -> Result<Option<PathBuf>, String> {
     if global_asm.is_empty() {
         return Ok(None);
@@ -146,7 +147,7 @@ pub(crate) fn compile_global_asm(
     global_asm.push('\n');
 
     let global_asm_object_file = add_file_stem_postfix(
-        config.output_filenames.temp_path_for_cgu(OutputType::Object, cgu_name),
+        config.output_filenames.temp_path_for_cgu(OutputType::Object, cgu_name, invocation_temp),
         ".asm",
     );
 
diff --git a/compiler/rustc_codegen_cranelift/src/lib.rs b/compiler/rustc_codegen_cranelift/src/lib.rs
index e7afaff3b42..9d9e790289c 100644
--- a/compiler/rustc_codegen_cranelift/src/lib.rs
+++ b/compiler/rustc_codegen_cranelift/src/lib.rs
@@ -124,6 +124,7 @@ impl<F: Fn() -> String> Drop for PrintOnPanic<F> {
 /// inside a single codegen unit with the exception of the Cranelift [`Module`](cranelift_module::Module).
 struct CodegenCx {
     output_filenames: Arc<OutputFilenames>,
+    invocation_temp: Option<String>,
     should_write_ir: bool,
     global_asm: String,
     inline_asm_index: usize,
@@ -142,6 +143,7 @@ impl CodegenCx {
         };
         CodegenCx {
             output_filenames: tcx.output_filenames(()).clone(),
+            invocation_temp: tcx.sess.invocation_temp.clone(),
             should_write_ir: crate::pretty_clif::should_write_ir(tcx),
             global_asm: String::new(),
             inline_asm_index: 0,
diff --git a/compiler/rustc_codegen_gcc/src/back/write.rs b/compiler/rustc_codegen_gcc/src/back/write.rs
index 3427b38ce83..16c895322e8 100644
--- a/compiler/rustc_codegen_gcc/src/back/write.rs
+++ b/compiler/rustc_codegen_gcc/src/back/write.rs
@@ -31,8 +31,16 @@ pub(crate) unsafe fn codegen(
         // TODO(antoyo): remove this environment variable.
         let fat_lto = env::var("EMBED_LTO_BITCODE").as_deref() == Ok("1");
 
-        let bc_out = cgcx.output_filenames.temp_path_for_cgu(OutputType::Bitcode, &module.name);
-        let obj_out = cgcx.output_filenames.temp_path_for_cgu(OutputType::Object, &module.name);
+        let bc_out = cgcx.output_filenames.temp_path_for_cgu(
+            OutputType::Bitcode,
+            &module.name,
+            cgcx.invocation_temp.as_deref(),
+        );
+        let obj_out = cgcx.output_filenames.temp_path_for_cgu(
+            OutputType::Object,
+            &module.name,
+            cgcx.invocation_temp.as_deref(),
+        );
 
         if config.bitcode_needed() {
             if fat_lto {
@@ -113,15 +121,22 @@ pub(crate) unsafe fn codegen(
         }
 
         if config.emit_ir {
-            let out =
-                cgcx.output_filenames.temp_path_for_cgu(OutputType::LlvmAssembly, &module.name);
+            let out = cgcx.output_filenames.temp_path_for_cgu(
+                OutputType::LlvmAssembly,
+                &module.name,
+                cgcx.invocation_temp.as_deref(),
+            );
             std::fs::write(out, "").expect("write file");
         }
 
         if config.emit_asm {
             let _timer =
                 cgcx.prof.generic_activity_with_arg("GCC_module_codegen_emit_asm", &*module.name);
-            let path = cgcx.output_filenames.temp_path_for_cgu(OutputType::Assembly, &module.name);
+            let path = cgcx.output_filenames.temp_path_for_cgu(
+                OutputType::Assembly,
+                &module.name,
+                cgcx.invocation_temp.as_deref(),
+            );
             context.compile_to_file(OutputKind::Assembler, path.to_str().expect("path to str"));
         }
 
@@ -235,6 +250,7 @@ pub(crate) unsafe fn codegen(
         config.emit_asm,
         config.emit_ir,
         &cgcx.output_filenames,
+        cgcx.invocation_temp.as_deref(),
     ))
 }
 
diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs
index c8921794711..18d221d232e 100644
--- a/compiler/rustc_codegen_llvm/src/back/write.rs
+++ b/compiler/rustc_codegen_llvm/src/back/write.rs
@@ -120,13 +120,17 @@ pub(crate) fn create_target_machine(tcx: TyCtxt<'_>, mod_name: &str) -> OwnedTar
             tcx.sess.split_debuginfo(),
             tcx.sess.opts.unstable_opts.split_dwarf_kind,
             mod_name,
+            tcx.sess.invocation_temp.as_deref(),
         )
     } else {
         None
     };
 
-    let output_obj_file =
-        Some(tcx.output_filenames(()).temp_path_for_cgu(OutputType::Object, mod_name));
+    let output_obj_file = Some(tcx.output_filenames(()).temp_path_for_cgu(
+        OutputType::Object,
+        mod_name,
+        tcx.sess.invocation_temp.as_deref(),
+    ));
     let config = TargetMachineFactoryConfig { split_dwarf_file, output_obj_file };
 
     target_machine_factory(
@@ -330,7 +334,11 @@ pub(crate) fn save_temp_bitcode(
         return;
     }
     let ext = format!("{name}.bc");
-    let path = cgcx.output_filenames.temp_path_ext_for_cgu(&ext, &module.name);
+    let path = cgcx.output_filenames.temp_path_ext_for_cgu(
+        &ext,
+        &module.name,
+        cgcx.invocation_temp.as_deref(),
+    );
     write_bitcode_to_file(module, &path)
 }
 
@@ -694,7 +702,11 @@ pub(crate) unsafe fn optimize(
     let _handlers = DiagnosticHandlers::new(cgcx, dcx, llcx, module, CodegenDiagnosticsStage::Opt);
 
     if config.emit_no_opt_bc {
-        let out = cgcx.output_filenames.temp_path_ext_for_cgu("no-opt.bc", &module.name);
+        let out = cgcx.output_filenames.temp_path_ext_for_cgu(
+            "no-opt.bc",
+            &module.name,
+            cgcx.invocation_temp.as_deref(),
+        );
         write_bitcode_to_file(module, &out)
     }
 
@@ -739,8 +751,11 @@ pub(crate) unsafe fn optimize(
         if let Some(thin_lto_buffer) = thin_lto_buffer {
             let thin_lto_buffer = unsafe { ThinBuffer::from_raw_ptr(thin_lto_buffer) };
             module.thin_lto_buffer = Some(thin_lto_buffer.data().to_vec());
-            let bc_summary_out =
-                cgcx.output_filenames.temp_path_for_cgu(OutputType::ThinLinkBitcode, &module.name);
+            let bc_summary_out = cgcx.output_filenames.temp_path_for_cgu(
+                OutputType::ThinLinkBitcode,
+                &module.name,
+                cgcx.invocation_temp.as_deref(),
+            );
             if config.emit_thin_lto_summary
                 && let Some(thin_link_bitcode_filename) = bc_summary_out.file_name()
             {
@@ -808,8 +823,16 @@ pub(crate) unsafe fn codegen(
         // copy it to the .o file, and delete the bitcode if it wasn't
         // otherwise requested.
 
-        let bc_out = cgcx.output_filenames.temp_path_for_cgu(OutputType::Bitcode, &module.name);
-        let obj_out = cgcx.output_filenames.temp_path_for_cgu(OutputType::Object, &module.name);
+        let bc_out = cgcx.output_filenames.temp_path_for_cgu(
+            OutputType::Bitcode,
+            &module.name,
+            cgcx.invocation_temp.as_deref(),
+        );
+        let obj_out = cgcx.output_filenames.temp_path_for_cgu(
+            OutputType::Object,
+            &module.name,
+            cgcx.invocation_temp.as_deref(),
+        );
 
         if config.bitcode_needed() {
             if config.emit_bc || config.emit_obj == EmitObj::Bitcode {
@@ -851,8 +874,11 @@ pub(crate) unsafe fn codegen(
         if config.emit_ir {
             let _timer =
                 cgcx.prof.generic_activity_with_arg("LLVM_module_codegen_emit_ir", &*module.name);
-            let out =
-                cgcx.output_filenames.temp_path_for_cgu(OutputType::LlvmAssembly, &module.name);
+            let out = cgcx.output_filenames.temp_path_for_cgu(
+                OutputType::LlvmAssembly,
+                &module.name,
+                cgcx.invocation_temp.as_deref(),
+            );
             let out_c = path_to_c_string(&out);
 
             extern "C" fn demangle_callback(
@@ -894,7 +920,11 @@ pub(crate) unsafe fn codegen(
         if config.emit_asm {
             let _timer =
                 cgcx.prof.generic_activity_with_arg("LLVM_module_codegen_emit_asm", &*module.name);
-            let path = cgcx.output_filenames.temp_path_for_cgu(OutputType::Assembly, &module.name);
+            let path = cgcx.output_filenames.temp_path_for_cgu(
+                OutputType::Assembly,
+                &module.name,
+                cgcx.invocation_temp.as_deref(),
+            );
 
             // We can't use the same module for asm and object code output,
             // because that triggers various errors like invalid IR or broken
@@ -924,7 +954,9 @@ pub(crate) unsafe fn codegen(
                     .prof
                     .generic_activity_with_arg("LLVM_module_codegen_emit_obj", &*module.name);
 
-                let dwo_out = cgcx.output_filenames.temp_path_dwo_for_cgu(&module.name);
+                let dwo_out = cgcx
+                    .output_filenames
+                    .temp_path_dwo_for_cgu(&module.name, cgcx.invocation_temp.as_deref());
                 let dwo_out = match (cgcx.split_debuginfo, cgcx.split_dwarf_kind) {
                     // Don't change how DWARF is emitted when disabled.
                     (SplitDebuginfo::Off, _) => None,
@@ -989,6 +1021,7 @@ pub(crate) unsafe fn codegen(
         config.emit_asm,
         config.emit_ir,
         &cgcx.output_filenames,
+        cgcx.invocation_temp.as_deref(),
     ))
 }
 
diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs b/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs
index 4b0ec4a6415..1eb8f367c54 100644
--- a/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs
+++ b/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs
@@ -911,6 +911,7 @@ pub(crate) fn build_compile_unit_di_node<'ll, 'tcx>(
             tcx.sess.split_debuginfo(),
             tcx.sess.opts.unstable_opts.split_dwarf_kind,
             codegen_unit_name,
+            tcx.sess.invocation_temp.as_deref(),
         ) {
         // We get a path relative to the working directory from split_dwarf_path
         Some(tcx.sess.source_map().path_mapping().to_real_filename(f))
diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs
index 2785664595f..8de68925cab 100644
--- a/compiler/rustc_codegen_ssa/src/back/link.rs
+++ b/compiler/rustc_codegen_ssa/src/back/link.rs
@@ -112,7 +112,12 @@ pub fn link_binary(
                 codegen_results.crate_info.local_crate_name,
             );
             let crate_name = format!("{}", codegen_results.crate_info.local_crate_name);
-            let out_filename = output.file_for_writing(outputs, OutputType::Exe, &crate_name);
+            let out_filename = output.file_for_writing(
+                outputs,
+                OutputType::Exe,
+                &crate_name,
+                sess.invocation_temp.as_deref(),
+            );
             match crate_type {
                 CrateType::Rlib => {
                     let _timer = sess.timer("link_rlib");
diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs
index fa698ff59a2..0fd4ed8475b 100644
--- a/compiler/rustc_codegen_ssa/src/back/write.rs
+++ b/compiler/rustc_codegen_ssa/src/back/write.rs
@@ -307,13 +307,17 @@ impl TargetMachineFactoryConfig {
                 cgcx.split_debuginfo,
                 cgcx.split_dwarf_kind,
                 module_name,
+                cgcx.invocation_temp.as_deref(),
             )
         } else {
             None
         };
 
-        let output_obj_file =
-            Some(cgcx.output_filenames.temp_path_for_cgu(OutputType::Object, module_name));
+        let output_obj_file = Some(cgcx.output_filenames.temp_path_for_cgu(
+            OutputType::Object,
+            module_name,
+            cgcx.invocation_temp.as_deref(),
+        ));
         TargetMachineFactoryConfig { split_dwarf_file, output_obj_file }
     }
 }
@@ -344,6 +348,7 @@ pub struct CodegenContext<B: WriteBackendMethods> {
     pub crate_types: Vec<CrateType>,
     pub each_linked_rlib_for_lto: Vec<(CrateNum, PathBuf)>,
     pub output_filenames: Arc<OutputFilenames>,
+    pub invocation_temp: Option<String>,
     pub regular_module_config: Arc<ModuleConfig>,
     pub metadata_module_config: Arc<ModuleConfig>,
     pub allocator_module_config: Arc<ModuleConfig>,
@@ -582,7 +587,11 @@ fn produce_final_output_artifacts(
         if let [module] = &compiled_modules.modules[..] {
             // 1) Only one codegen unit. In this case it's no difficulty
             //    to copy `foo.0.x` to `foo.x`.
-            let path = crate_output.temp_path_for_cgu(output_type, &module.name);
+            let path = crate_output.temp_path_for_cgu(
+                output_type,
+                &module.name,
+                sess.invocation_temp.as_deref(),
+            );
             let output = crate_output.path(output_type);
             if !output_type.is_text_output() && output.is_tty() {
                 sess.dcx()
@@ -959,7 +968,12 @@ fn execute_copy_from_cache_work_item<B: ExtraBackendMethods>(
         module.source.saved_files.get("dwo").as_ref().and_then(|saved_dwarf_object_file| {
             let dwarf_obj_out = cgcx
                 .output_filenames
-                .split_dwarf_path(cgcx.split_debuginfo, cgcx.split_dwarf_kind, &module.name)
+                .split_dwarf_path(
+                    cgcx.split_debuginfo,
+                    cgcx.split_dwarf_kind,
+                    &module.name,
+                    cgcx.invocation_temp.as_deref(),
+                )
                 .expect(
                     "saved dwarf object in work product but `split_dwarf_path` returned `None`",
                 );
@@ -969,7 +983,11 @@ fn execute_copy_from_cache_work_item<B: ExtraBackendMethods>(
     let mut 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);
+            let output_path = cgcx.output_filenames.temp_path_for_cgu(
+                output_type,
+                &module.name,
+                cgcx.invocation_temp.as_deref(),
+            );
             load_from_incr_comp_dir(output_path, &saved_file)
         } else {
             None
@@ -1214,6 +1232,7 @@ fn start_executing_work<B: ExtraBackendMethods>(
         split_dwarf_kind: tcx.sess.opts.unstable_opts.split_dwarf_kind,
         parallel: backend.supports_parallel() && !sess.opts.unstable_opts.no_parallel_backend,
         pointer_size: tcx.data_layout.pointer_size,
+        invocation_temp: sess.invocation_temp.clone(),
     };
 
     // This is the "main loop" of parallel work happening for parallel codegen.
diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs
index 511c79c93a2..597d2340116 100644
--- a/compiler/rustc_codegen_ssa/src/base.rs
+++ b/compiler/rustc_codegen_ssa/src/base.rs
@@ -640,9 +640,11 @@ pub fn codegen_crate<B: ExtraBackendMethods>(
         let metadata_cgu_name =
             cgu_name_builder.build_cgu_name(LOCAL_CRATE, &["crate"], Some("metadata")).to_string();
         tcx.sess.time("write_compressed_metadata", || {
-            let file_name = tcx
-                .output_filenames(())
-                .temp_path_for_cgu(OutputType::Metadata, &metadata_cgu_name);
+            let file_name = tcx.output_filenames(()).temp_path_for_cgu(
+                OutputType::Metadata,
+                &metadata_cgu_name,
+                tcx.sess.invocation_temp.as_deref(),
+            );
             let data = create_compressed_metadata_file(
                 tcx.sess,
                 &metadata,
diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs
index 491fe4a57a4..153935f2531 100644
--- a/compiler/rustc_codegen_ssa/src/lib.rs
+++ b/compiler/rustc_codegen_ssa/src/lib.rs
@@ -105,14 +105,19 @@ impl<M> ModuleCodegen<M> {
         emit_asm: bool,
         emit_ir: bool,
         outputs: &OutputFilenames,
+        invocation_temp: Option<&str>,
     ) -> CompiledModule {
-        let object = emit_obj.then(|| outputs.temp_path_for_cgu(OutputType::Object, &self.name));
-        let dwarf_object = emit_dwarf_obj.then(|| outputs.temp_path_dwo_for_cgu(&self.name));
-        let bytecode = emit_bc.then(|| outputs.temp_path_for_cgu(OutputType::Bitcode, &self.name));
-        let assembly =
-            emit_asm.then(|| outputs.temp_path_for_cgu(OutputType::Assembly, &self.name));
-        let llvm_ir =
-            emit_ir.then(|| outputs.temp_path_for_cgu(OutputType::LlvmAssembly, &self.name));
+        let object = emit_obj
+            .then(|| outputs.temp_path_for_cgu(OutputType::Object, &self.name, invocation_temp));
+        let dwarf_object =
+            emit_dwarf_obj.then(|| outputs.temp_path_dwo_for_cgu(&self.name, invocation_temp));
+        let bytecode = emit_bc
+            .then(|| outputs.temp_path_for_cgu(OutputType::Bitcode, &self.name, invocation_temp));
+        let assembly = emit_asm
+            .then(|| outputs.temp_path_for_cgu(OutputType::Assembly, &self.name, invocation_temp));
+        let llvm_ir = emit_ir.then(|| {
+            outputs.temp_path_for_cgu(OutputType::LlvmAssembly, &self.name, invocation_temp)
+        });
 
         CompiledModule {
             name: self.name.clone(),
diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs
index 747e36b6a1a..ad7fb1ad05f 100644
--- a/compiler/rustc_interface/src/passes.rs
+++ b/compiler/rustc_interface/src/passes.rs
@@ -800,6 +800,7 @@ pub fn create_and_enter_global_ctxt<T, F: for<'tcx> FnOnce(TyCtxt<'tcx>) -> T>(
         sess.opts.cg.metadata.clone(),
         sess.cfg_version,
     );
+
     let outputs = util::build_output_filenames(&pre_configured_attrs, sess);
 
     let dep_type = DepsType { dep_names: rustc_query_impl::dep_kind_names() };
diff --git a/compiler/rustc_session/Cargo.toml b/compiler/rustc_session/Cargo.toml
index a087725d34d..63772a32222 100644
--- a/compiler/rustc_session/Cargo.toml
+++ b/compiler/rustc_session/Cargo.toml
@@ -7,6 +7,7 @@ edition = "2024"
 # tidy-alphabetical-start
 bitflags = "2.4.1"
 getopts = "0.2"
+rand = "0.9.0"
 rustc_abi = { path = "../rustc_abi" }
 rustc_ast = { path = "../rustc_ast" }
 rustc_data_structures = { path = "../rustc_data_structures" }
diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs
index 946b1201e05..bdd54a15147 100644
--- a/compiler/rustc_session/src/config.rs
+++ b/compiler/rustc_session/src/config.rs
@@ -1016,10 +1016,13 @@ impl OutFileName {
         outputs: &OutputFilenames,
         flavor: OutputType,
         codegen_unit_name: &str,
+        invocation_temp: Option<&str>,
     ) -> PathBuf {
         match *self {
             OutFileName::Real(ref path) => path.clone(),
-            OutFileName::Stdout => outputs.temp_path_for_cgu(flavor, codegen_unit_name),
+            OutFileName::Stdout => {
+                outputs.temp_path_for_cgu(flavor, codegen_unit_name, invocation_temp)
+            }
         }
     }
 
@@ -1094,21 +1097,41 @@ impl OutputFilenames {
     /// Gets the path where a compilation artifact of the given type for the
     /// given codegen unit should be placed on disk. If codegen_unit_name is
     /// None, a path distinct from those of any codegen unit will be generated.
-    pub fn temp_path_for_cgu(&self, flavor: OutputType, codegen_unit_name: &str) -> PathBuf {
+    pub fn temp_path_for_cgu(
+        &self,
+        flavor: OutputType,
+        codegen_unit_name: &str,
+        invocation_temp: Option<&str>,
+    ) -> PathBuf {
         let extension = flavor.extension();
-        self.temp_path_ext_for_cgu(extension, codegen_unit_name)
+        self.temp_path_ext_for_cgu(extension, codegen_unit_name, invocation_temp)
     }
 
     /// Like `temp_path`, but specifically for dwarf objects.
-    pub fn temp_path_dwo_for_cgu(&self, codegen_unit_name: &str) -> PathBuf {
-        self.temp_path_ext_for_cgu(DWARF_OBJECT_EXT, codegen_unit_name)
+    pub fn temp_path_dwo_for_cgu(
+        &self,
+        codegen_unit_name: &str,
+        invocation_temp: Option<&str>,
+    ) -> PathBuf {
+        self.temp_path_ext_for_cgu(DWARF_OBJECT_EXT, codegen_unit_name, invocation_temp)
     }
 
     /// Like `temp_path`, but also supports things where there is no corresponding
     /// OutputType, like noopt-bitcode or lto-bitcode.
-    pub fn temp_path_ext_for_cgu(&self, ext: &str, codegen_unit_name: &str) -> PathBuf {
+    pub fn temp_path_ext_for_cgu(
+        &self,
+        ext: &str,
+        codegen_unit_name: &str,
+        invocation_temp: Option<&str>,
+    ) -> PathBuf {
         let mut extension = codegen_unit_name.to_string();
 
+        // Append `.{invocation_temp}` to ensure temporary files are unique.
+        if let Some(rng) = invocation_temp {
+            extension.push('.');
+            extension.push_str(rng);
+        }
+
         // FIXME: This is sketchy that we're not appending `.rcgu` when the ext is empty.
         // Append `.rcgu.{ext}`.
         if !ext.is_empty() {
@@ -1144,9 +1167,10 @@ impl OutputFilenames {
         split_debuginfo_kind: SplitDebuginfo,
         split_dwarf_kind: SplitDwarfKind,
         cgu_name: &str,
+        invocation_temp: Option<&str>,
     ) -> Option<PathBuf> {
-        let obj_out = self.temp_path_for_cgu(OutputType::Object, cgu_name);
-        let dwo_out = self.temp_path_dwo_for_cgu(cgu_name);
+        let obj_out = self.temp_path_for_cgu(OutputType::Object, cgu_name, invocation_temp);
+        let dwo_out = self.temp_path_dwo_for_cgu(cgu_name, invocation_temp);
         match (split_debuginfo_kind, split_dwarf_kind) {
             (SplitDebuginfo::Off, SplitDwarfKind::Single | SplitDwarfKind::Split) => None,
             // Single mode doesn't change how DWARF is emitted, but does add Split DWARF attributes
diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs
index fb4a437a487..1359f7eb7bb 100644
--- a/compiler/rustc_session/src/session.rs
+++ b/compiler/rustc_session/src/session.rs
@@ -6,6 +6,8 @@ use std::sync::Arc;
 use std::sync::atomic::AtomicBool;
 use std::{env, fmt, io};
 
+use rand::{RngCore, rng};
+use rustc_data_structures::base_n::{CASE_INSENSITIVE, ToBaseN};
 use rustc_data_structures::flock;
 use rustc_data_structures::fx::{FxHashMap, FxIndexSet};
 use rustc_data_structures::profiling::{SelfProfiler, SelfProfilerRef};
@@ -203,6 +205,14 @@ pub struct Session {
 
     target_filesearch: FileSearch,
     host_filesearch: FileSearch,
+
+    /// A random string generated per invocation of rustc.
+    ///
+    /// This is prepended to all temporary files so that they do not collide
+    /// during concurrent invocations of rustc, or past invocations that were
+    /// preserved with a flag like `-C save-temps`, since these files may be
+    /// hard linked.
+    pub invocation_temp: Option<String>,
 }
 
 #[derive(PartialEq, Eq, PartialOrd, Ord)]
@@ -1117,6 +1127,12 @@ pub fn build_session(
     let target_filesearch =
         filesearch::FileSearch::new(&sopts.search_paths, &target_tlib_path, &target);
     let host_filesearch = filesearch::FileSearch::new(&sopts.search_paths, &host_tlib_path, &host);
+
+    let invocation_temp = sopts
+        .incremental
+        .as_ref()
+        .map(|_| rng().next_u32().to_base_fixed_len(CASE_INSENSITIVE).to_string());
+
     let sess = Session {
         target,
         host,
@@ -1140,6 +1156,7 @@ pub fn build_session(
         expanded_args,
         target_filesearch,
         host_filesearch,
+        invocation_temp,
     };
 
     validate_commandline_args_with_session_available(&sess);
diff --git a/tests/run-make/dirty-incr-due-to-hard-link/rmake.rs b/tests/run-make/dirty-incr-due-to-hard-link/rmake.rs
new file mode 100644
index 00000000000..942b667814a
--- /dev/null
+++ b/tests/run-make/dirty-incr-due-to-hard-link/rmake.rs
@@ -0,0 +1,32 @@
+//@ only-x86_64-unknown-linux-gnu
+
+// Regression test for the incremental bug in <https://github.com/rust-lang/rust/issues/139407>.
+//
+// A detailed explanation is described in <https://github.com/rust-lang/rust/pull/139453>,
+// however the gist of the issue is that hard-linking temporary files can interact strangely
+// across incremental sessions that are not finalized due to errors originating from the
+// codegen backend.
+
+use run_make_support::{run, rustc};
+
+fn main() {
+    let mk_rustc = || {
+        let mut rustc = rustc();
+        rustc.input("test.rs").incremental("incr").arg("-Csave-temps").output("test");
+        rustc
+    };
+
+    // Revision 1
+    mk_rustc().cfg("rpass1").run();
+
+    run("test");
+
+    // Revision 2
+    mk_rustc().cfg("cfail2").run_fail();
+    // Expected to fail.
+
+    // Revision 3
+    mk_rustc().cfg("rpass3").run();
+
+    run("test");
+}
diff --git a/tests/run-make/dirty-incr-due-to-hard-link/test.rs b/tests/run-make/dirty-incr-due-to-hard-link/test.rs
new file mode 100644
index 00000000000..dba5a6bbc82
--- /dev/null
+++ b/tests/run-make/dirty-incr-due-to-hard-link/test.rs
@@ -0,0 +1,31 @@
+#[inline(never)]
+#[cfg(any(rpass1, rpass3))]
+fn a() -> i32 {
+    0
+}
+
+#[cfg(any(cfail2))]
+fn a() -> i32 {
+    1
+}
+
+fn main() {
+    evil::evil();
+    assert_eq!(a(), 0);
+}
+
+mod evil {
+    #[cfg(any(rpass1, rpass3))]
+    pub fn evil() {
+        unsafe {
+            std::arch::asm!("/*  */");
+        }
+    }
+
+    #[cfg(any(cfail2))]
+    pub fn evil() {
+        unsafe {
+            std::arch::asm!("missing");
+        }
+    }
+}