Skip to content

Commit e1b06f7

Browse files
committedApr 11, 2025·
Auto merge of #139453 - compiler-errors:incr, r=jieyouxu
Prepend temp files with per-invocation random string to avoid temp filename conflicts #139407 uncovered a very subtle unsoundness with incremental codegen, failing compilation sessions (due to assembler errors), and the "prefer hard linking over copying files" strategy we use in the compiler for file management. Specifically, imagine we're building a single file 3 times, all with `-Csave-temps -Cincremental=...`. Let's call the object file we're building for the codegen unit for `main` "`XXX.o`" just for clarity since it's probably some gigantic hash name: ``` #[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"); } } } ``` Session 1 (`rpass1`): * Type-check, borrow-check, etc. * Serialize the dep graph to the incremental working directory `.../s-...-working/`. * Codegen object file to a temp file `XXX.rcgu.o` which is spit out in the cwd. * Hard-link[^1] `XXX.rcgu.o` to the incremental working directory `.../s-...-working/XXX.o`. * Save-temps option means we don't delete `XXX.rgcu.o`. * Link the binary and stuff. * Finalize[^2] the working incremental session by renaming `.../s-...-working` to ` s-...-asjkdhsjakd` (some other finalized incr comp session dir name). Session 2 (`cfail2`): * Load artifacts from the previous *finalized* incremental session, namely the dep graph. * Type-check, borrow-check, etc. since the file has changed, so most dep graph nodes are red. * Serialize the dep graph to the incremental working directory `.../s-...-working/`. * Codegen object file to a temp file `XXX.rcgu.o`. **HERE IS THE PROBLEM**: The hard-link is still set up to point to the inode from `XXX.o` from the first session, so this also modifies the `XXX.o` in the previous finalized session directory. * Codegen emits an error b/c `missing` is not an instruction, so we abort before finalizing the incremental session. Specifically, this means that the *previous* session is the last finalized session. Session 3 (`rpass3`): * Load artifacts from the previous *finalized* incremental session, namely the dep graph. NOTE that this is from session 1. * All the dep graph nodes are green since we are basically replaying session 1. * codegen object file `XXX.o`, which is detected as *reused* from session 1 since dep nodes were green. That means we **reuse** `XXX.o` which had been dirtied from session 2. * Link the binary and stuff. This results in a binary which reuses some of the build artifacts from session 2, but thinks it's from session 1. At this point, I hope it's clear to see that the incremental results from session 1 were dirtied from session 2, but we reuse them as if session 1 was the previous (finalized) incremental session we ran. This is at best really buggy, and at worst **unsound**. This isn't limited to `-C save-temps`, since there are other combinations of flags that may keep around temporary files (hard linked) in the working directory (like `-C debuginfo=1 -C split-debuginfo=unpacked` on darwin, for example). --- This PR implements a fix which is to prepend temp filenames with a random string that is generated per invocation of rustc. This string is not *deterministic*, but temporary files are transient anyways, so I don't believe this is a problem. That means that temp files are now something like... `{crate-name}.{cgu}.{invocation_temp}.rcgu.o`, where `{invocation_temp}` is the new temporary string we generate per invocation of rustc. Fixes #139407 [^1]: https://github.com/rust-lang/rust/blob/175dcc7773d65c1b1542c351392080f48c05799f/compiler/rustc_fs_util/src/lib.rs#L60 [^2]: https://github.com/rust-lang/rust/blob/175dcc7773d65c1b1542c351392080f48c05799f/compiler/rustc_incremental/src/persist/fs.rs#L1-L40
2 parents 71b68da + 9c372d8 commit e1b06f7

File tree

20 files changed

+285
-97
lines changed

20 files changed

+285
-97
lines changed
 

‎Cargo.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4444,6 +4444,7 @@ dependencies = [
44444444
"bitflags",
44454445
"getopts",
44464446
"libc",
4447+
"rand 0.9.0",
44474448
"rustc_abi",
44484449
"rustc_ast",
44494450
"rustc_data_structures",

‎compiler/rustc_codegen_cranelift/src/driver/aot.rs

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,11 @@ fn produce_final_output_artifacts(
169169
if codegen_results.modules.len() == 1 {
170170
// 1) Only one codegen unit. In this case it's no difficulty
171171
// to copy `foo.0.x` to `foo.x`.
172-
let module_name = Some(&codegen_results.modules[0].name[..]);
173-
let path = crate_output.temp_path(output_type, module_name);
172+
let path = crate_output.temp_path_for_cgu(
173+
output_type,
174+
&codegen_results.modules[0].name,
175+
sess.invocation_temp.as_deref(),
176+
);
174177
let output = crate_output.path(output_type);
175178
if !output_type.is_text_output() && output.is_tty() {
176179
sess.dcx()
@@ -183,22 +186,16 @@ fn produce_final_output_artifacts(
183186
ensure_removed(sess.dcx(), &path);
184187
}
185188
} else {
186-
let extension = crate_output
187-
.temp_path(output_type, None)
188-
.extension()
189-
.unwrap()
190-
.to_str()
191-
.unwrap()
192-
.to_owned();
193-
194189
if crate_output.outputs.contains_explicit_name(&output_type) {
195190
// 2) Multiple codegen units, with `--emit foo=some_name`. We have
196191
// no good solution for this case, so warn the user.
197-
sess.dcx().emit_warn(ssa_errors::IgnoringEmitPath { extension });
192+
sess.dcx()
193+
.emit_warn(ssa_errors::IgnoringEmitPath { extension: output_type.extension() });
198194
} else if crate_output.single_output_file.is_some() {
199195
// 3) Multiple codegen units, with `-o some_name`. We have
200196
// no good solution for this case, so warn the user.
201-
sess.dcx().emit_warn(ssa_errors::IgnoringOutput { extension });
197+
sess.dcx()
198+
.emit_warn(ssa_errors::IgnoringOutput { extension: output_type.extension() });
202199
} else {
203200
// 4) Multiple codegen units, but no explicit name. We
204201
// just leave the `foo.0.x` files in place.
@@ -351,6 +348,7 @@ fn make_module(sess: &Session, name: String) -> UnwindModule<ObjectModule> {
351348

352349
fn emit_cgu(
353350
output_filenames: &OutputFilenames,
351+
invocation_temp: Option<&str>,
354352
prof: &SelfProfilerRef,
355353
name: String,
356354
module: UnwindModule<ObjectModule>,
@@ -366,6 +364,7 @@ fn emit_cgu(
366364

367365
let module_regular = emit_module(
368366
output_filenames,
367+
invocation_temp,
369368
prof,
370369
product.object,
371370
ModuleKind::Regular,
@@ -391,6 +390,7 @@ fn emit_cgu(
391390

392391
fn emit_module(
393392
output_filenames: &OutputFilenames,
393+
invocation_temp: Option<&str>,
394394
prof: &SelfProfilerRef,
395395
mut object: cranelift_object::object::write::Object<'_>,
396396
kind: ModuleKind,
@@ -409,7 +409,7 @@ fn emit_module(
409409
object.set_section_data(comment_section, producer, 1);
410410
}
411411

412-
let tmp_file = output_filenames.temp_path(OutputType::Object, Some(&name));
412+
let tmp_file = output_filenames.temp_path_for_cgu(OutputType::Object, &name, invocation_temp);
413413
let file = match File::create(&tmp_file) {
414414
Ok(file) => file,
415415
Err(err) => return Err(format!("error creating object file: {}", err)),
@@ -449,8 +449,11 @@ fn reuse_workproduct_for_cgu(
449449
cgu: &CodegenUnit<'_>,
450450
) -> Result<ModuleCodegenResult, String> {
451451
let work_product = cgu.previous_work_product(tcx);
452-
let obj_out_regular =
453-
tcx.output_filenames(()).temp_path(OutputType::Object, Some(cgu.name().as_str()));
452+
let obj_out_regular = tcx.output_filenames(()).temp_path_for_cgu(
453+
OutputType::Object,
454+
cgu.name().as_str(),
455+
tcx.sess.invocation_temp.as_deref(),
456+
);
454457
let source_file_regular = rustc_incremental::in_incr_comp_dir_sess(
455458
&tcx.sess,
456459
&work_product.saved_files.get("o").expect("no saved object file in work product"),
@@ -595,13 +598,19 @@ fn module_codegen(
595598

596599
let global_asm_object_file =
597600
profiler.generic_activity_with_arg("compile assembly", &*cgu_name).run(|| {
598-
crate::global_asm::compile_global_asm(&global_asm_config, &cgu_name, &cx.global_asm)
601+
crate::global_asm::compile_global_asm(
602+
&global_asm_config,
603+
&cgu_name,
604+
&cx.global_asm,
605+
cx.invocation_temp.as_deref(),
606+
)
599607
})?;
600608

601609
let codegen_result =
602610
profiler.generic_activity_with_arg("write object file", &*cgu_name).run(|| {
603611
emit_cgu(
604612
&global_asm_config.output_filenames,
613+
cx.invocation_temp.as_deref(),
605614
&profiler,
606615
cgu_name,
607616
module,
@@ -626,8 +635,11 @@ fn emit_metadata_module(tcx: TyCtxt<'_>, metadata: &EncodedMetadata) -> Compiled
626635
.as_str()
627636
.to_string();
628637

629-
let tmp_file =
630-
tcx.output_filenames(()).temp_path(OutputType::Metadata, Some(&metadata_cgu_name));
638+
let tmp_file = tcx.output_filenames(()).temp_path_for_cgu(
639+
OutputType::Metadata,
640+
&metadata_cgu_name,
641+
tcx.sess.invocation_temp.as_deref(),
642+
);
631643

632644
let symbol_name = rustc_middle::middle::exported_symbols::metadata_symbol_name(tcx);
633645
let obj = create_compressed_metadata_file(tcx.sess, metadata, &symbol_name);
@@ -657,6 +669,7 @@ fn emit_allocator_module(tcx: TyCtxt<'_>) -> Option<CompiledModule> {
657669

658670
match emit_module(
659671
tcx.output_filenames(()),
672+
tcx.sess.invocation_temp.as_deref(),
660673
&tcx.sess.prof,
661674
product.object,
662675
ModuleKind::Allocator,

‎compiler/rustc_codegen_cranelift/src/global_asm.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ pub(crate) fn compile_global_asm(
132132
config: &GlobalAsmConfig,
133133
cgu_name: &str,
134134
global_asm: &str,
135+
invocation_temp: Option<&str>,
135136
) -> Result<Option<PathBuf>, String> {
136137
if global_asm.is_empty() {
137138
return Ok(None);
@@ -146,7 +147,7 @@ pub(crate) fn compile_global_asm(
146147
global_asm.push('\n');
147148

148149
let global_asm_object_file = add_file_stem_postfix(
149-
config.output_filenames.temp_path(OutputType::Object, Some(cgu_name)),
150+
config.output_filenames.temp_path_for_cgu(OutputType::Object, cgu_name, invocation_temp),
150151
".asm",
151152
);
152153

‎compiler/rustc_codegen_cranelift/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ impl<F: Fn() -> String> Drop for PrintOnPanic<F> {
124124
/// inside a single codegen unit with the exception of the Cranelift [`Module`](cranelift_module::Module).
125125
struct CodegenCx {
126126
output_filenames: Arc<OutputFilenames>,
127+
invocation_temp: Option<String>,
127128
should_write_ir: bool,
128129
global_asm: String,
129130
inline_asm_index: usize,
@@ -142,6 +143,7 @@ impl CodegenCx {
142143
};
143144
CodegenCx {
144145
output_filenames: tcx.output_filenames(()).clone(),
146+
invocation_temp: tcx.sess.invocation_temp.clone(),
145147
should_write_ir: crate::pretty_clif::should_write_ir(tcx),
146148
global_asm: String::new(),
147149
inline_asm_index: 0,

‎compiler/rustc_codegen_gcc/src/back/write.rs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,23 @@ pub(crate) unsafe fn codegen(
2424
{
2525
let context = &module.module_llvm.context;
2626

27-
let module_name = module.name.clone();
28-
2927
let should_combine_object_files = module.module_llvm.should_combine_object_files;
3028

31-
let module_name = Some(&module_name[..]);
32-
3329
// NOTE: Only generate object files with GIMPLE when this environment variable is set for
3430
// now because this requires a particular setup (same gcc/lto1/lto-wrapper commit as libgccjit).
3531
// TODO(antoyo): remove this environment variable.
3632
let fat_lto = env::var("EMBED_LTO_BITCODE").as_deref() == Ok("1");
3733

38-
let bc_out = cgcx.output_filenames.temp_path(OutputType::Bitcode, module_name);
39-
let obj_out = cgcx.output_filenames.temp_path(OutputType::Object, module_name);
34+
let bc_out = cgcx.output_filenames.temp_path_for_cgu(
35+
OutputType::Bitcode,
36+
&module.name,
37+
cgcx.invocation_temp.as_deref(),
38+
);
39+
let obj_out = cgcx.output_filenames.temp_path_for_cgu(
40+
OutputType::Object,
41+
&module.name,
42+
cgcx.invocation_temp.as_deref(),
43+
);
4044

4145
if config.bitcode_needed() {
4246
if fat_lto {
@@ -117,14 +121,22 @@ pub(crate) unsafe fn codegen(
117121
}
118122

119123
if config.emit_ir {
120-
let out = cgcx.output_filenames.temp_path(OutputType::LlvmAssembly, module_name);
124+
let out = cgcx.output_filenames.temp_path_for_cgu(
125+
OutputType::LlvmAssembly,
126+
&module.name,
127+
cgcx.invocation_temp.as_deref(),
128+
);
121129
std::fs::write(out, "").expect("write file");
122130
}
123131

124132
if config.emit_asm {
125133
let _timer =
126134
cgcx.prof.generic_activity_with_arg("GCC_module_codegen_emit_asm", &*module.name);
127-
let path = cgcx.output_filenames.temp_path(OutputType::Assembly, module_name);
135+
let path = cgcx.output_filenames.temp_path_for_cgu(
136+
OutputType::Assembly,
137+
&module.name,
138+
cgcx.invocation_temp.as_deref(),
139+
);
128140
context.compile_to_file(OutputKind::Assembler, path.to_str().expect("path to str"));
129141
}
130142

@@ -238,6 +250,7 @@ pub(crate) unsafe fn codegen(
238250
config.emit_asm,
239251
config.emit_ir,
240252
&cgcx.output_filenames,
253+
cgcx.invocation_temp.as_deref(),
241254
))
242255
}
243256

‎compiler/rustc_codegen_llvm/src/back/write.rs

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -119,14 +119,18 @@ pub(crate) fn create_target_machine(tcx: TyCtxt<'_>, mod_name: &str) -> OwnedTar
119119
tcx.output_filenames(()).split_dwarf_path(
120120
tcx.sess.split_debuginfo(),
121121
tcx.sess.opts.unstable_opts.split_dwarf_kind,
122-
Some(mod_name),
122+
mod_name,
123+
tcx.sess.invocation_temp.as_deref(),
123124
)
124125
} else {
125126
None
126127
};
127128

128-
let output_obj_file =
129-
Some(tcx.output_filenames(()).temp_path(OutputType::Object, Some(mod_name)));
129+
let output_obj_file = Some(tcx.output_filenames(()).temp_path_for_cgu(
130+
OutputType::Object,
131+
mod_name,
132+
tcx.sess.invocation_temp.as_deref(),
133+
));
130134
let config = TargetMachineFactoryConfig { split_dwarf_file, output_obj_file };
131135

132136
target_machine_factory(
@@ -330,8 +334,11 @@ pub(crate) fn save_temp_bitcode(
330334
return;
331335
}
332336
let ext = format!("{name}.bc");
333-
let cgu = Some(&module.name[..]);
334-
let path = cgcx.output_filenames.temp_path_ext(&ext, cgu);
337+
let path = cgcx.output_filenames.temp_path_ext_for_cgu(
338+
&ext,
339+
&module.name,
340+
cgcx.invocation_temp.as_deref(),
341+
);
335342
write_bitcode_to_file(module, &path)
336343
}
337344

@@ -694,11 +701,12 @@ pub(crate) unsafe fn optimize(
694701
let llcx = &*module.module_llvm.llcx;
695702
let _handlers = DiagnosticHandlers::new(cgcx, dcx, llcx, module, CodegenDiagnosticsStage::Opt);
696703

697-
let module_name = module.name.clone();
698-
let module_name = Some(&module_name[..]);
699-
700704
if config.emit_no_opt_bc {
701-
let out = cgcx.output_filenames.temp_path_ext("no-opt.bc", module_name);
705+
let out = cgcx.output_filenames.temp_path_ext_for_cgu(
706+
"no-opt.bc",
707+
&module.name,
708+
cgcx.invocation_temp.as_deref(),
709+
);
702710
write_bitcode_to_file(module, &out)
703711
}
704712

@@ -743,8 +751,11 @@ pub(crate) unsafe fn optimize(
743751
if let Some(thin_lto_buffer) = thin_lto_buffer {
744752
let thin_lto_buffer = unsafe { ThinBuffer::from_raw_ptr(thin_lto_buffer) };
745753
module.thin_lto_buffer = Some(thin_lto_buffer.data().to_vec());
746-
let bc_summary_out =
747-
cgcx.output_filenames.temp_path(OutputType::ThinLinkBitcode, module_name);
754+
let bc_summary_out = cgcx.output_filenames.temp_path_for_cgu(
755+
OutputType::ThinLinkBitcode,
756+
&module.name,
757+
cgcx.invocation_temp.as_deref(),
758+
);
748759
if config.emit_thin_lto_summary
749760
&& let Some(thin_link_bitcode_filename) = bc_summary_out.file_name()
750761
{
@@ -801,8 +812,6 @@ pub(crate) unsafe fn codegen(
801812
let llmod = module.module_llvm.llmod();
802813
let llcx = &*module.module_llvm.llcx;
803814
let tm = &*module.module_llvm.tm;
804-
let module_name = module.name.clone();
805-
let module_name = Some(&module_name[..]);
806815
let _handlers =
807816
DiagnosticHandlers::new(cgcx, dcx, llcx, &module, CodegenDiagnosticsStage::Codegen);
808817

@@ -814,8 +823,16 @@ pub(crate) unsafe fn codegen(
814823
// copy it to the .o file, and delete the bitcode if it wasn't
815824
// otherwise requested.
816825

817-
let bc_out = cgcx.output_filenames.temp_path(OutputType::Bitcode, module_name);
818-
let obj_out = cgcx.output_filenames.temp_path(OutputType::Object, module_name);
826+
let bc_out = cgcx.output_filenames.temp_path_for_cgu(
827+
OutputType::Bitcode,
828+
&module.name,
829+
cgcx.invocation_temp.as_deref(),
830+
);
831+
let obj_out = cgcx.output_filenames.temp_path_for_cgu(
832+
OutputType::Object,
833+
&module.name,
834+
cgcx.invocation_temp.as_deref(),
835+
);
819836

820837
if config.bitcode_needed() {
821838
if config.emit_bc || config.emit_obj == EmitObj::Bitcode {
@@ -857,7 +874,11 @@ pub(crate) unsafe fn codegen(
857874
if config.emit_ir {
858875
let _timer =
859876
cgcx.prof.generic_activity_with_arg("LLVM_module_codegen_emit_ir", &*module.name);
860-
let out = cgcx.output_filenames.temp_path(OutputType::LlvmAssembly, module_name);
877+
let out = cgcx.output_filenames.temp_path_for_cgu(
878+
OutputType::LlvmAssembly,
879+
&module.name,
880+
cgcx.invocation_temp.as_deref(),
881+
);
861882
let out_c = path_to_c_string(&out);
862883

863884
extern "C" fn demangle_callback(
@@ -899,7 +920,11 @@ pub(crate) unsafe fn codegen(
899920
if config.emit_asm {
900921
let _timer =
901922
cgcx.prof.generic_activity_with_arg("LLVM_module_codegen_emit_asm", &*module.name);
902-
let path = cgcx.output_filenames.temp_path(OutputType::Assembly, module_name);
923+
let path = cgcx.output_filenames.temp_path_for_cgu(
924+
OutputType::Assembly,
925+
&module.name,
926+
cgcx.invocation_temp.as_deref(),
927+
);
903928

904929
// We can't use the same module for asm and object code output,
905930
// because that triggers various errors like invalid IR or broken
@@ -929,7 +954,9 @@ pub(crate) unsafe fn codegen(
929954
.prof
930955
.generic_activity_with_arg("LLVM_module_codegen_emit_obj", &*module.name);
931956

932-
let dwo_out = cgcx.output_filenames.temp_path_dwo(module_name);
957+
let dwo_out = cgcx
958+
.output_filenames
959+
.temp_path_dwo_for_cgu(&module.name, cgcx.invocation_temp.as_deref());
933960
let dwo_out = match (cgcx.split_debuginfo, cgcx.split_dwarf_kind) {
934961
// Don't change how DWARF is emitted when disabled.
935962
(SplitDebuginfo::Off, _) => None,
@@ -994,6 +1021,7 @@ pub(crate) unsafe fn codegen(
9941021
config.emit_asm,
9951022
config.emit_ir,
9961023
&cgcx.output_filenames,
1024+
cgcx.invocation_temp.as_deref(),
9971025
))
9981026
}
9991027

‎compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -910,7 +910,8 @@ pub(crate) fn build_compile_unit_di_node<'ll, 'tcx>(
910910
&& let Some(f) = output_filenames.split_dwarf_path(
911911
tcx.sess.split_debuginfo(),
912912
tcx.sess.opts.unstable_opts.split_dwarf_kind,
913-
Some(codegen_unit_name),
913+
codegen_unit_name,
914+
tcx.sess.invocation_temp.as_deref(),
914915
) {
915916
// We get a path relative to the working directory from split_dwarf_path
916917
Some(tcx.sess.source_map().path_mapping().to_real_filename(f))

‎compiler/rustc_codegen_ssa/src/back/link.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,12 @@ pub fn link_binary(
112112
codegen_results.crate_info.local_crate_name,
113113
);
114114
let crate_name = format!("{}", codegen_results.crate_info.local_crate_name);
115-
let out_filename =
116-
output.file_for_writing(outputs, OutputType::Exe, Some(crate_name.as_str()));
115+
let out_filename = output.file_for_writing(
116+
outputs,
117+
OutputType::Exe,
118+
&crate_name,
119+
sess.invocation_temp.as_deref(),
120+
);
117121
match crate_type {
118122
CrateType::Rlib => {
119123
let _timer = sess.timer("link_rlib");

‎compiler/rustc_codegen_ssa/src/back/write.rs

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -306,14 +306,18 @@ impl TargetMachineFactoryConfig {
306306
cgcx.output_filenames.split_dwarf_path(
307307
cgcx.split_debuginfo,
308308
cgcx.split_dwarf_kind,
309-
Some(module_name),
309+
module_name,
310+
cgcx.invocation_temp.as_deref(),
310311
)
311312
} else {
312313
None
313314
};
314315

315-
let output_obj_file =
316-
Some(cgcx.output_filenames.temp_path(OutputType::Object, Some(module_name)));
316+
let output_obj_file = Some(cgcx.output_filenames.temp_path_for_cgu(
317+
OutputType::Object,
318+
module_name,
319+
cgcx.invocation_temp.as_deref(),
320+
));
317321
TargetMachineFactoryConfig { split_dwarf_file, output_obj_file }
318322
}
319323
}
@@ -344,6 +348,7 @@ pub struct CodegenContext<B: WriteBackendMethods> {
344348
pub crate_types: Vec<CrateType>,
345349
pub each_linked_rlib_for_lto: Vec<(CrateNum, PathBuf)>,
346350
pub output_filenames: Arc<OutputFilenames>,
351+
pub invocation_temp: Option<String>,
347352
pub regular_module_config: Arc<ModuleConfig>,
348353
pub metadata_module_config: Arc<ModuleConfig>,
349354
pub allocator_module_config: Arc<ModuleConfig>,
@@ -582,8 +587,11 @@ fn produce_final_output_artifacts(
582587
if let [module] = &compiled_modules.modules[..] {
583588
// 1) Only one codegen unit. In this case it's no difficulty
584589
// to copy `foo.0.x` to `foo.x`.
585-
let module_name = Some(&module.name[..]);
586-
let path = crate_output.temp_path(output_type, module_name);
590+
let path = crate_output.temp_path_for_cgu(
591+
output_type,
592+
&module.name,
593+
sess.invocation_temp.as_deref(),
594+
);
587595
let output = crate_output.path(output_type);
588596
if !output_type.is_text_output() && output.is_tty() {
589597
sess.dcx()
@@ -596,22 +604,15 @@ fn produce_final_output_artifacts(
596604
ensure_removed(sess.dcx(), &path);
597605
}
598606
} else {
599-
let extension = crate_output
600-
.temp_path(output_type, None)
601-
.extension()
602-
.unwrap()
603-
.to_str()
604-
.unwrap()
605-
.to_owned();
606-
607607
if crate_output.outputs.contains_explicit_name(&output_type) {
608608
// 2) Multiple codegen units, with `--emit foo=some_name`. We have
609609
// no good solution for this case, so warn the user.
610-
sess.dcx().emit_warn(errors::IgnoringEmitPath { extension });
610+
sess.dcx()
611+
.emit_warn(errors::IgnoringEmitPath { extension: output_type.extension() });
611612
} else if crate_output.single_output_file.is_some() {
612613
// 3) Multiple codegen units, with `-o some_name`. We have
613614
// no good solution for this case, so warn the user.
614-
sess.dcx().emit_warn(errors::IgnoringOutput { extension });
615+
sess.dcx().emit_warn(errors::IgnoringOutput { extension: output_type.extension() });
615616
} else {
616617
// 4) Multiple codegen units, but no explicit name. We
617618
// just leave the `foo.0.x` files in place.
@@ -967,7 +968,12 @@ fn execute_copy_from_cache_work_item<B: ExtraBackendMethods>(
967968
module.source.saved_files.get("dwo").as_ref().and_then(|saved_dwarf_object_file| {
968969
let dwarf_obj_out = cgcx
969970
.output_filenames
970-
.split_dwarf_path(cgcx.split_debuginfo, cgcx.split_dwarf_kind, Some(&module.name))
971+
.split_dwarf_path(
972+
cgcx.split_debuginfo,
973+
cgcx.split_dwarf_kind,
974+
&module.name,
975+
cgcx.invocation_temp.as_deref(),
976+
)
971977
.expect(
972978
"saved dwarf object in work product but `split_dwarf_path` returned `None`",
973979
);
@@ -977,7 +983,11 @@ fn execute_copy_from_cache_work_item<B: ExtraBackendMethods>(
977983
let mut load_from_incr_cache = |perform, output_type: OutputType| {
978984
if perform {
979985
let saved_file = module.source.saved_files.get(output_type.extension())?;
980-
let output_path = cgcx.output_filenames.temp_path(output_type, Some(&module.name));
986+
let output_path = cgcx.output_filenames.temp_path_for_cgu(
987+
output_type,
988+
&module.name,
989+
cgcx.invocation_temp.as_deref(),
990+
);
981991
load_from_incr_comp_dir(output_path, &saved_file)
982992
} else {
983993
None
@@ -1222,6 +1232,7 @@ fn start_executing_work<B: ExtraBackendMethods>(
12221232
split_dwarf_kind: tcx.sess.opts.unstable_opts.split_dwarf_kind,
12231233
parallel: backend.supports_parallel() && !sess.opts.unstable_opts.no_parallel_backend,
12241234
pointer_size: tcx.data_layout.pointer_size,
1235+
invocation_temp: sess.invocation_temp.clone(),
12251236
};
12261237

12271238
// This is the "main loop" of parallel work happening for parallel codegen.

‎compiler/rustc_codegen_ssa/src/base.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -640,8 +640,11 @@ pub fn codegen_crate<B: ExtraBackendMethods>(
640640
let metadata_cgu_name =
641641
cgu_name_builder.build_cgu_name(LOCAL_CRATE, &["crate"], Some("metadata")).to_string();
642642
tcx.sess.time("write_compressed_metadata", || {
643-
let file_name =
644-
tcx.output_filenames(()).temp_path(OutputType::Metadata, Some(&metadata_cgu_name));
643+
let file_name = tcx.output_filenames(()).temp_path_for_cgu(
644+
OutputType::Metadata,
645+
&metadata_cgu_name,
646+
tcx.sess.invocation_temp.as_deref(),
647+
);
645648
let data = create_compressed_metadata_file(
646649
tcx.sess,
647650
&metadata,

‎compiler/rustc_codegen_ssa/src/errors.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,13 +277,13 @@ pub struct BinaryOutputToTty {
277277
#[derive(Diagnostic)]
278278
#[diag(codegen_ssa_ignoring_emit_path)]
279279
pub struct IgnoringEmitPath {
280-
pub extension: String,
280+
pub extension: &'static str,
281281
}
282282

283283
#[derive(Diagnostic)]
284284
#[diag(codegen_ssa_ignoring_output)]
285285
pub struct IgnoringOutput {
286-
pub extension: String,
286+
pub extension: &'static str,
287287
}
288288

289289
#[derive(Diagnostic)]

‎compiler/rustc_codegen_ssa/src/lib.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,19 @@ impl<M> ModuleCodegen<M> {
105105
emit_asm: bool,
106106
emit_ir: bool,
107107
outputs: &OutputFilenames,
108+
invocation_temp: Option<&str>,
108109
) -> CompiledModule {
109-
let object = emit_obj.then(|| outputs.temp_path(OutputType::Object, Some(&self.name)));
110-
let dwarf_object = emit_dwarf_obj.then(|| outputs.temp_path_dwo(Some(&self.name)));
111-
let bytecode = emit_bc.then(|| outputs.temp_path(OutputType::Bitcode, Some(&self.name)));
112-
let assembly = emit_asm.then(|| outputs.temp_path(OutputType::Assembly, Some(&self.name)));
113-
let llvm_ir =
114-
emit_ir.then(|| outputs.temp_path(OutputType::LlvmAssembly, Some(&self.name)));
110+
let object = emit_obj
111+
.then(|| outputs.temp_path_for_cgu(OutputType::Object, &self.name, invocation_temp));
112+
let dwarf_object =
113+
emit_dwarf_obj.then(|| outputs.temp_path_dwo_for_cgu(&self.name, invocation_temp));
114+
let bytecode = emit_bc
115+
.then(|| outputs.temp_path_for_cgu(OutputType::Bitcode, &self.name, invocation_temp));
116+
let assembly = emit_asm
117+
.then(|| outputs.temp_path_for_cgu(OutputType::Assembly, &self.name, invocation_temp));
118+
let llvm_ir = emit_ir.then(|| {
119+
outputs.temp_path_for_cgu(OutputType::LlvmAssembly, &self.name, invocation_temp)
120+
});
115121

116122
CompiledModule {
117123
name: self.name.clone(),

‎compiler/rustc_interface/src/passes.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,7 @@ pub fn create_and_enter_global_ctxt<T, F: for<'tcx> FnOnce(TyCtxt<'tcx>) -> T>(
800800
sess.opts.cg.metadata.clone(),
801801
sess.cfg_version,
802802
);
803+
803804
let outputs = util::build_output_filenames(&pre_configured_attrs, sess);
804805

805806
let dep_type = DepsType { dep_names: rustc_query_impl::dep_kind_names() };

‎compiler/rustc_middle/src/ty/error.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ impl<'tcx> TyCtxt<'tcx> {
279279
p.hash(&mut s);
280280
let hash = s.finish();
281281
*path = Some(path.take().unwrap_or_else(|| {
282-
self.output_filenames(()).temp_path_ext(&format!("long-type-{hash}.txt"), None)
282+
self.output_filenames(()).temp_path_for_diagnostic(&format!("long-type-{hash}.txt"))
283283
}));
284284
let Ok(mut file) =
285285
File::options().create(true).read(true).append(true).open(&path.as_ref().unwrap())

‎compiler/rustc_middle/src/ty/print/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ pub fn shrunk_instance_name<'tcx>(
382382
return (s, None);
383383
}
384384

385-
let path = tcx.output_filenames(()).temp_path_ext("long-type.txt", None);
385+
let path = tcx.output_filenames(()).temp_path_for_diagnostic("long-type.txt");
386386
let written_to_path = std::fs::write(&path, s).ok().map(|_| path);
387387

388388
(shrunk, written_to_path)

‎compiler/rustc_session/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ edition = "2024"
77
# tidy-alphabetical-start
88
bitflags = "2.4.1"
99
getopts = "0.2"
10+
rand = "0.9.0"
1011
rustc_abi = { path = "../rustc_abi" }
1112
rustc_ast = { path = "../rustc_ast" }
1213
rustc_data_structures = { path = "../rustc_data_structures" }

‎compiler/rustc_session/src/config.rs

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,11 +1015,14 @@ impl OutFileName {
10151015
&self,
10161016
outputs: &OutputFilenames,
10171017
flavor: OutputType,
1018-
codegen_unit_name: Option<&str>,
1018+
codegen_unit_name: &str,
1019+
invocation_temp: Option<&str>,
10191020
) -> PathBuf {
10201021
match *self {
10211022
OutFileName::Real(ref path) => path.clone(),
1022-
OutFileName::Stdout => outputs.temp_path(flavor, codegen_unit_name),
1023+
OutFileName::Stdout => {
1024+
outputs.temp_path_for_cgu(flavor, codegen_unit_name, invocation_temp)
1025+
}
10231026
}
10241027
}
10251028

@@ -1094,40 +1097,59 @@ impl OutputFilenames {
10941097
/// Gets the path where a compilation artifact of the given type for the
10951098
/// given codegen unit should be placed on disk. If codegen_unit_name is
10961099
/// None, a path distinct from those of any codegen unit will be generated.
1097-
pub fn temp_path(&self, flavor: OutputType, codegen_unit_name: Option<&str>) -> PathBuf {
1100+
pub fn temp_path_for_cgu(
1101+
&self,
1102+
flavor: OutputType,
1103+
codegen_unit_name: &str,
1104+
invocation_temp: Option<&str>,
1105+
) -> PathBuf {
10981106
let extension = flavor.extension();
1099-
self.temp_path_ext(extension, codegen_unit_name)
1107+
self.temp_path_ext_for_cgu(extension, codegen_unit_name, invocation_temp)
11001108
}
11011109

11021110
/// Like `temp_path`, but specifically for dwarf objects.
1103-
pub fn temp_path_dwo(&self, codegen_unit_name: Option<&str>) -> PathBuf {
1104-
self.temp_path_ext(DWARF_OBJECT_EXT, codegen_unit_name)
1111+
pub fn temp_path_dwo_for_cgu(
1112+
&self,
1113+
codegen_unit_name: &str,
1114+
invocation_temp: Option<&str>,
1115+
) -> PathBuf {
1116+
self.temp_path_ext_for_cgu(DWARF_OBJECT_EXT, codegen_unit_name, invocation_temp)
11051117
}
11061118

11071119
/// Like `temp_path`, but also supports things where there is no corresponding
11081120
/// OutputType, like noopt-bitcode or lto-bitcode.
1109-
pub fn temp_path_ext(&self, ext: &str, codegen_unit_name: Option<&str>) -> PathBuf {
1110-
let mut extension = String::new();
1121+
pub fn temp_path_ext_for_cgu(
1122+
&self,
1123+
ext: &str,
1124+
codegen_unit_name: &str,
1125+
invocation_temp: Option<&str>,
1126+
) -> PathBuf {
1127+
let mut extension = codegen_unit_name.to_string();
11111128

1112-
if let Some(codegen_unit_name) = codegen_unit_name {
1113-
extension.push_str(codegen_unit_name);
1129+
// Append `.{invocation_temp}` to ensure temporary files are unique.
1130+
if let Some(rng) = invocation_temp {
1131+
extension.push('.');
1132+
extension.push_str(rng);
11141133
}
11151134

1135+
// FIXME: This is sketchy that we're not appending `.rcgu` when the ext is empty.
1136+
// Append `.rcgu.{ext}`.
11161137
if !ext.is_empty() {
1117-
if !extension.is_empty() {
1118-
extension.push('.');
1119-
extension.push_str(RUST_CGU_EXT);
1120-
extension.push('.');
1121-
}
1122-
1138+
extension.push('.');
1139+
extension.push_str(RUST_CGU_EXT);
1140+
extension.push('.');
11231141
extension.push_str(ext);
11241142
}
11251143

11261144
let temps_directory = self.temps_directory.as_ref().unwrap_or(&self.out_directory);
1127-
11281145
self.with_directory_and_extension(temps_directory, &extension)
11291146
}
11301147

1148+
pub fn temp_path_for_diagnostic(&self, ext: &str) -> PathBuf {
1149+
let temps_directory = self.temps_directory.as_ref().unwrap_or(&self.out_directory);
1150+
self.with_directory_and_extension(temps_directory, &ext)
1151+
}
1152+
11311153
pub fn with_extension(&self, extension: &str) -> PathBuf {
11321154
self.with_directory_and_extension(&self.out_directory, extension)
11331155
}
@@ -1144,10 +1166,11 @@ impl OutputFilenames {
11441166
&self,
11451167
split_debuginfo_kind: SplitDebuginfo,
11461168
split_dwarf_kind: SplitDwarfKind,
1147-
cgu_name: Option<&str>,
1169+
cgu_name: &str,
1170+
invocation_temp: Option<&str>,
11481171
) -> Option<PathBuf> {
1149-
let obj_out = self.temp_path(OutputType::Object, cgu_name);
1150-
let dwo_out = self.temp_path_dwo(cgu_name);
1172+
let obj_out = self.temp_path_for_cgu(OutputType::Object, cgu_name, invocation_temp);
1173+
let dwo_out = self.temp_path_dwo_for_cgu(cgu_name, invocation_temp);
11511174
match (split_debuginfo_kind, split_dwarf_kind) {
11521175
(SplitDebuginfo::Off, SplitDwarfKind::Single | SplitDwarfKind::Split) => None,
11531176
// Single mode doesn't change how DWARF is emitted, but does add Split DWARF attributes

‎compiler/rustc_session/src/session.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ use std::sync::Arc;
66
use std::sync::atomic::AtomicBool;
77
use std::{env, fmt, io};
88

9+
use rand::{RngCore, rng};
10+
use rustc_data_structures::base_n::{CASE_INSENSITIVE, ToBaseN};
911
use rustc_data_structures::flock;
1012
use rustc_data_structures::fx::{FxHashMap, FxIndexSet};
1113
use rustc_data_structures::profiling::{SelfProfiler, SelfProfilerRef};
@@ -203,6 +205,14 @@ pub struct Session {
203205

204206
target_filesearch: FileSearch,
205207
host_filesearch: FileSearch,
208+
209+
/// A random string generated per invocation of rustc.
210+
///
211+
/// This is prepended to all temporary files so that they do not collide
212+
/// during concurrent invocations of rustc, or past invocations that were
213+
/// preserved with a flag like `-C save-temps`, since these files may be
214+
/// hard linked.
215+
pub invocation_temp: Option<String>,
206216
}
207217

208218
#[derive(PartialEq, Eq, PartialOrd, Ord)]
@@ -1117,6 +1127,12 @@ pub fn build_session(
11171127
let target_filesearch =
11181128
filesearch::FileSearch::new(&sopts.search_paths, &target_tlib_path, &target);
11191129
let host_filesearch = filesearch::FileSearch::new(&sopts.search_paths, &host_tlib_path, &host);
1130+
1131+
let invocation_temp = sopts
1132+
.incremental
1133+
.as_ref()
1134+
.map(|_| rng().next_u32().to_base_fixed_len(CASE_INSENSITIVE).to_string());
1135+
11201136
let sess = Session {
11211137
target,
11221138
host,
@@ -1140,6 +1156,7 @@ pub fn build_session(
11401156
expanded_args,
11411157
target_filesearch,
11421158
host_filesearch,
1159+
invocation_temp,
11431160
};
11441161

11451162
validate_commandline_args_with_session_available(&sess);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//@ only-x86_64-unknown-linux-gnu
2+
3+
// Regression test for the incremental bug in <https://github.com/rust-lang/rust/issues/139407>.
4+
//
5+
// A detailed explanation is described in <https://github.com/rust-lang/rust/pull/139453>,
6+
// however the gist of the issue is that hard-linking temporary files can interact strangely
7+
// across incremental sessions that are not finalized due to errors originating from the
8+
// codegen backend.
9+
10+
use run_make_support::{run, rustc};
11+
12+
fn main() {
13+
let mk_rustc = || {
14+
let mut rustc = rustc();
15+
rustc.input("test.rs").incremental("incr").arg("-Csave-temps").output("test");
16+
rustc
17+
};
18+
19+
// Revision 1
20+
mk_rustc().cfg("rpass1").run();
21+
22+
run("test");
23+
24+
// Revision 2
25+
mk_rustc().cfg("cfail2").run_fail();
26+
// Expected to fail.
27+
28+
// Revision 3
29+
mk_rustc().cfg("rpass3").run();
30+
31+
run("test");
32+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#[inline(never)]
2+
#[cfg(any(rpass1, rpass3))]
3+
fn a() -> i32 {
4+
0
5+
}
6+
7+
#[cfg(any(cfail2))]
8+
fn a() -> i32 {
9+
1
10+
}
11+
12+
fn main() {
13+
evil::evil();
14+
assert_eq!(a(), 0);
15+
}
16+
17+
mod evil {
18+
#[cfg(any(rpass1, rpass3))]
19+
pub fn evil() {
20+
unsafe {
21+
std::arch::asm!("/* */");
22+
}
23+
}
24+
25+
#[cfg(any(cfail2))]
26+
pub fn evil() {
27+
unsafe {
28+
std::arch::asm!("missing");
29+
}
30+
}
31+
}

0 commit comments

Comments
 (0)
Please sign in to comment.