diff --git a/.mailmap b/.mailmap index d72c2e39c26a3..33000e11dcefa 100644 --- a/.mailmap +++ b/.mailmap @@ -64,6 +64,7 @@ Arthur Cohen Arthur Silva Arthur Woimbée Artyom Pavlov +Austin K. Austin Seipp Ayaz Hafiz Aydin Kim aydin.kim diff --git a/Cargo.lock b/Cargo.lock index 6866b2c8e4d92..49ab9451ba5e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3493,9 +3493,9 @@ dependencies = [ [[package]] name = "rustc-build-sysroot" -version = "0.5.13" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "569d545953ee9a1ab9d3e9112e961739ff0cfd50bce06b126937395940be69c9" +checksum = "0d0ed7817483b026efc0492f4ccca177f00fb711537f37943cc412bbf12682b4" dependencies = [ "anyhow", "rustc_version", diff --git a/compiler/rustc_codegen_cranelift/src/driver/aot.rs b/compiler/rustc_codegen_cranelift/src/driver/aot.rs index a62dff641cfec..463b2d5cb2a37 100644 --- a/compiler/rustc_codegen_cranelift/src/driver/aot.rs +++ b/compiler/rustc_codegen_cranelift/src/driver/aot.rs @@ -22,8 +22,8 @@ use rustc_errors::{DiagCtxt, DiagCtxtHandle}; use rustc_middle::dep_graph::WorkProduct; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; use rustc_middle::mono::{MonoItem, MonoItemData, Visibility}; -use rustc_session::Session; use rustc_session::config::{OptLevel, OutputFilenames, OutputType}; +use rustc_session::{BorrowedIncrCompSession, Session}; use rustc_span::Symbol; use crate::base::CodegenedFunction; @@ -337,6 +337,7 @@ impl WriteBackendMethods for AotDriver { fn run_thin_lto( _cgcx: &CodegenContext, _prof: &SelfProfilerRef, + _incr_comp_session: Option<&BorrowedIncrCompSession>, _dcx: rustc_errors::DiagCtxtHandle<'_>, _exported_symbols_for_lto: &[String], _each_linked_rlib_for_lto: &[PathBuf], diff --git a/compiler/rustc_codegen_gcc/src/back/lto.rs b/compiler/rustc_codegen_gcc/src/back/lto.rs index baf1fda02e258..28cabc36dbe63 100644 --- a/compiler/rustc_codegen_gcc/src/back/lto.rs +++ b/compiler/rustc_codegen_gcc/src/back/lto.rs @@ -148,11 +148,11 @@ fn fat_lto( for module in modules { match module { FatLtoInput::InMemory(m) => in_memory.push(m), - FatLtoInput::Serialized { name, bitcode_path } => { - info!("pushing serialized module {:?}", name); + FatLtoInput::Serialized { wp, bitcode_path } => { + info!("pushing serialized module {:?}", wp.cgu_name); serialized_modules.push(( SerializedModule::from_file(&bitcode_path), - CString::new(name).unwrap(), + CString::new(wp.cgu_name).unwrap(), )); } } diff --git a/compiler/rustc_codegen_gcc/src/lib.rs b/compiler/rustc_codegen_gcc/src/lib.rs index b82d05dc211aa..59585c520a6cd 100644 --- a/compiler/rustc_codegen_gcc/src/lib.rs +++ b/compiler/rustc_codegen_gcc/src/lib.rs @@ -93,7 +93,9 @@ use rustc_errors::{DiagCtxt, DiagCtxtHandle}; use rustc_middle::dep_graph::{WorkProduct, WorkProductMap}; use rustc_middle::ty::TyCtxt; use rustc_session::config::{OptLevel, OutputFilenames}; -use rustc_session::{CodegenBackendInit, EarlySession, IncrCompSession, Session}; +use rustc_session::{ + BorrowedIncrCompSession, CodegenBackendInit, EarlySession, IncrCompSession, Session, +}; use rustc_span::{Symbol, sym}; use rustc_target::spec::{RelocModel, TargetTuple}; use tempfile::TempDir; @@ -404,6 +406,7 @@ impl WriteBackendMethods for GccCodegenBackend { fn run_thin_lto( _cgcx: &CodegenContext, _prof: &SelfProfilerRef, + _incr_comp_session: Option<&BorrowedIncrCompSession>, _dcx: DiagCtxtHandle<'_>, // FIXME(bjorn3): Limit LTO exports to these symbols _exported_symbols_for_lto: &[String], diff --git a/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs b/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs index 3f89777bb28db..ade7fa7dff307 100644 --- a/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs +++ b/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs @@ -20,7 +20,9 @@ use rustc_metadata::EncodedMetadata; use rustc_middle::dep_graph::{WorkProduct, WorkProductMap}; use rustc_middle::ty::TyCtxt; use rustc_session::config::{OptLevel, OutputFilenames, PrintKind, PrintRequest}; -use rustc_session::{CodegenBackendInit, EarlySession, IncrCompSession, Session}; +use rustc_session::{ + BorrowedIncrCompSession, CodegenBackendInit, EarlySession, IncrCompSession, Session, +}; use rustc_span::{Symbol, sym}; use rustc_target::spec::{RelocModel, TlsModel}; @@ -120,6 +122,7 @@ impl WriteBackendMethods for LlvmCodegenBackend { fn run_thin_lto( cgcx: &CodegenContext, prof: &SelfProfilerRef, + incr_comp_session: Option<&BorrowedIncrCompSession>, dcx: DiagCtxtHandle<'_>, exported_symbols_for_lto: &[String], each_linked_rlib_for_lto: &[PathBuf], @@ -128,6 +131,7 @@ impl WriteBackendMethods for LlvmCodegenBackend { back::lto::run_thin( cgcx, prof, + incr_comp_session, dcx, exported_symbols_for_lto, each_linked_rlib_for_lto, @@ -380,21 +384,35 @@ impl CodegenBackend for LlvmCodegenBackend { }); } - (compiled_modules, work_products) - } + if sess.codegen_units().as_usize() == 1 && sess.opts.unstable_opts.time_llvm_passes { + let timings = + llvm::build_string(|s| unsafe { llvm::LLVMRustPrintPassTimings(s) }).unwrap(); + print!("{timings}"); + } - fn print_pass_timings(&self) { - let timings = llvm::build_string(|s| unsafe { llvm::LLVMRustPrintPassTimings(s) }).unwrap(); - print!("{timings}"); - } + if sess.print_llvm_stats() { + let stats = + llvm::build_string(|s| unsafe { llvm::LLVMRustPrintStatistics(s) }).unwrap(); + print!("{stats}"); + } - fn print_statistics(&self) { - let stats = llvm::build_string(|s| unsafe { llvm::LLVMRustPrintStatistics(s) }).unwrap(); - print!("{stats}"); - } + if let Some(out_path) = sess.print_llvm_stats_json() { + let llvm_stats_json = + llvm::build_string(|s| unsafe { llvm::LLVMRustPrintStatisticsJSON(s) }).unwrap(); + + if !llvm_stats_json.is_empty() { + if let Err(e) = std::fs::write(&out_path, llvm_stats_json) { + sess.dcx().err(format!("failed to write stats to {out_path}: {e}")); + } + } else { + sess.dcx().warn(format!( + "requested to print LLVM statistics to JSON file {out_path}, but the codegen backend \ + did not provide any statistics", + )); + } + } - fn print_statistics_json(&self) -> String { - llvm::build_string(|s| unsafe { llvm::LLVMRustPrintStatisticsJSON(s) }).unwrap() + (compiled_modules, work_products) } fn link( diff --git a/compiler/rustc_codegen_llvm/src/back/lto.rs b/compiler/rustc_codegen_llvm/src/back/lto.rs index d52e34d30fb7d..27a3377f06f26 100644 --- a/compiler/rustc_codegen_llvm/src/back/lto.rs +++ b/compiler/rustc_codegen_llvm/src/back/lto.rs @@ -19,7 +19,7 @@ use rustc_data_structures::memmap::Mmap; use rustc_data_structures::profiling::SelfProfilerRef; use rustc_errors::{DiagCtxt, DiagCtxtHandle}; use rustc_middle::dep_graph::WorkProduct; -use rustc_session::config; +use rustc_session::{BorrowedIncrCompSession, config}; use rustc_span::bug; use rustc_structures::SanitizerSet; use tracing::{debug, info}; @@ -184,6 +184,7 @@ pub(crate) fn run_fat( pub(crate) fn run_thin( cgcx: &CodegenContext, prof: &SelfProfilerRef, + incr_comp_session: Option<&BorrowedIncrCompSession>, dcx: DiagCtxtHandle<'_>, exported_symbols_for_lto: &[String], each_linked_rlib_for_lto: &[PathBuf], @@ -199,7 +200,7 @@ pub(crate) fn run_thin( is deferred to the linker" ); } - thin_lto(cgcx, prof, dcx, modules, upstream_modules, &symbols_below_threshold) + thin_lto(prof, incr_comp_session, dcx, modules, upstream_modules, &symbols_below_threshold) } fn fat_lto( @@ -225,11 +226,11 @@ fn fat_lto( for module in modules { match module { FatLtoInput::InMemory(m) => in_memory.push(m), - FatLtoInput::Serialized { name, bitcode_path } => { - info!("pushing serialized module {:?}", name); + FatLtoInput::Serialized { wp, bitcode_path } => { + info!("pushing serialized module {:?}", wp.cgu_name); serialized_modules.push(( SerializedModule::from_file(&bitcode_path), - CString::new(name).unwrap(), + CString::new(wp.cgu_name).unwrap(), )); } } @@ -371,8 +372,8 @@ fn fat_lto( /// all of the `LtoModuleCodegen` units returned below and destroyed once /// they all go out of scope. fn thin_lto( - cgcx: &CodegenContext, prof: &SelfProfilerRef, + incr_comp_session: Option<&BorrowedIncrCompSession>, dcx: DiagCtxtHandle<'_>, modules: Vec>, serialized_modules: Vec<(SerializedModule, CString)>, @@ -400,7 +401,7 @@ fn thin_lto( for (i, module) in modules.into_iter().enumerate() { let (name, buffer) = match module { - ThinLtoInput::Red { name, buffer } => (name, buffer), + ThinLtoInput::Red { wp, buffer } => (wp.cgu_name, buffer), ThinLtoInput::Green { wp, bitcode_path } => { (wp.cgu_name, SerializedModule::from_file(&bitcode_path)) } @@ -463,13 +464,12 @@ fn thin_lto( info!("thin LTO data created"); - 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 new_key_map_path = incr_comp_session.as_ref().map(|incr_comp_session| { + incr_comp_session.new_session_directory.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 prev_key_map = if let Some(ref old_incr_comp_session_dir) = incr_comp_session + .and_then(|incr_comp_session| incr_comp_session.old_session_directory.as_deref()) { let old_path = old_incr_comp_session_dir.join(THIN_LTO_KEYS_INCR_COMP_FILE_NAME); @@ -481,7 +481,7 @@ fn thin_lto( assert!(green_modules.is_empty()); None }; - let curr_key_map = if cgcx.new_incr_comp_session_dir.is_some() { + let curr_key_map = if incr_comp_session.is_some() { ThinLTOKeysMap::from_thin_lto_modules(&data, &thin_modules, &module_names) } else { assert!(green_modules.is_empty()); @@ -506,8 +506,7 @@ fn thin_lto( if let (Some(prev_key_map), true) = (prev_key_map.as_ref(), green_modules.contains_key(module_name)) { - assert!(cgcx.old_incr_comp_session_dir.is_some()); - assert!(cgcx.new_incr_comp_session_dir.is_some()); + assert!(incr_comp_session.unwrap().old_session_directory.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 diff --git a/compiler/rustc_codegen_ssa/src/assert_module_sources.rs b/compiler/rustc_codegen_ssa/src/assert_module_sources.rs index 431783552bcd0..792ab9873378a 100644 --- a/compiler/rustc_codegen_ssa/src/assert_module_sources.rs +++ b/compiler/rustc_codegen_ssa/src/assert_module_sources.rs @@ -111,7 +111,7 @@ impl<'tcx> AssertModuleSource<'tcx> { if !self.check_config(cfg) { debug!("check_attr: config does not match, ignoring attr"); - return; + continue; } let user_path = module.as_str(); diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index 04be7956e8278..683bbfe0efff7 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -9,6 +9,7 @@ use std::{assert_matches, fs, io, str, thread}; use rustc_abi::Size; use rustc_data_structures::jobserver::{self, Acquired}; use rustc_data_structures::profiling::{SelfProfilerRef, VerboseTimingGuard}; +use rustc_data_structures::unord::UnordMap; use rustc_errors::emitter::Emitter; use rustc_errors::{ Diag, DiagCtxt, DiagCtxtHandle, DiagInner, FatalError, FatalErrorMarker, Level, @@ -20,12 +21,12 @@ use rustc_incremental::{ }; use rustc_macros::{Decodable, Encodable}; use rustc_metadata::fs::copy_to_stdout; -use rustc_middle::dep_graph::{WorkProduct, WorkProductMap}; +use rustc_middle::dep_graph::{WorkProduct, WorkProductId, WorkProductMap}; use rustc_middle::ty::TyCtxt; use rustc_session::config::{ self, Lto, OptLevel, OutFileName, OutputFilenames, OutputType, Passes, SwitchWithOptPath, }; -use rustc_session::{IncrCompSession, Session}; +use rustc_session::{BorrowedIncrCompSession, IncrCompSession, Session}; use rustc_span::source_map::SourceMap; use rustc_span::{BytePos, FileName, InnerSpan, Span, SyntaxContext, bug}; use rustc_structures::CrateType; @@ -348,12 +349,6 @@ 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 new_incr_comp_session_dir: Option, /// `Some(limit)` if the codegen should be run in parallel. /// /// Depends on [`WriteBackendMethods::supports_parallel()`] and `--jobs-backend`. @@ -363,6 +358,7 @@ pub struct CodegenContext { fn generate_thin_lto_work( cgcx: &CodegenContext, prof: &SelfProfilerRef, + incr_comp_session: Option<&BorrowedIncrCompSession>, dcx: DiagCtxtHandle<'_>, exported_symbols_for_lto: &[String], each_linked_rlib_for_lto: &[PathBuf], @@ -373,6 +369,7 @@ fn generate_thin_lto_work( let (lto_modules, copy_jobs) = B::run_thin_lto( cgcx, prof, + incr_comp_session, dcx, exported_symbols_for_lto, each_linked_rlib_for_lto, @@ -769,17 +766,25 @@ pub(crate) enum WorkItemResult { /// The backend has finished compiling a CGU, which now needs to go through /// thin LTO. - NeedsThinLto(String, B::ModuleBuffer), + NeedsThinLto(WorkProduct, B::ModuleBuffer), } pub enum FatLtoInput { - Serialized { name: String, bitcode_path: PathBuf }, + Serialized { wp: WorkProduct, bitcode_path: PathBuf }, InMemory(ModuleCodegen), } pub enum ThinLtoInput { - Red { name: String, buffer: SerializedModule }, - Green { wp: WorkProduct, bitcode_path: PathBuf }, + Red { + /// Contains only pre-LTO bitcode + wp: WorkProduct, + buffer: SerializedModule, + }, + Green { + /// Contains pre-LTO bitcode and post-LTO artifacts + wp: WorkProduct, + bitcode_path: PathBuf, + }, } /// Actual LTO type we end up choosing based on multiple factors. @@ -819,6 +824,7 @@ pub(crate) fn compute_per_cgu_lto_type( fn execute_optimize_work_item( cgcx: &CodegenContext, prof: &SelfProfilerRef, + new_incr_comp_session_dir: Option<&Path>, shared_emitter: SharedEmitter, mut module: ModuleCodegen, ) -> WorkItemResult { @@ -838,7 +844,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.new_incr_comp_session_dir.as_ref().map(|path| path.join(&filename)) + new_incr_comp_session_dir.map(|path| path.join(&filename)) } else { None }; @@ -855,7 +861,16 @@ fn execute_optimize_work_item( panic!("Error writing pre-lto-bitcode file `{}`: {}", path.display(), e); }); } - WorkItemResult::NeedsThinLto(module.name, thin_buffer) + WorkItemResult::NeedsThinLto( + WorkProduct { + cgu_name: module.name.clone(), + saved_files: UnordMap::from_iter([( + PRE_LTO_BC_EXT.to_owned(), + pre_lto_bitcode_filename(&module.name), + )]), + }, + thin_buffer, + ) } ComputedLtoType::Fat => match bitcode { Some(path) => { @@ -864,7 +879,13 @@ fn execute_optimize_work_item( panic!("Error writing pre-lto-bitcode file `{}`: {}", path.display(), e); }); WorkItemResult::NeedsFatLto(FatLtoInput::Serialized { - name: module.name, + wp: WorkProduct { + cgu_name: module.name.clone(), + saved_files: UnordMap::from_iter([( + PRE_LTO_BC_EXT.to_owned(), + pre_lto_bitcode_filename(&module.name), + )]), + }, bitcode_path: path, }) } @@ -876,6 +897,7 @@ fn execute_optimize_work_item( fn execute_copy_from_cache_work_item( cgcx: &CodegenContext, prof: &SelfProfilerRef, + old_incr_comp_session_dir: &Path, shared_emitter: SharedEmitter, module: CachedModuleCodegen, ) -> CompiledModule { @@ -885,10 +907,8 @@ 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.old_incr_comp_session_dir.as_ref().unwrap(); - 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); + let source_file_in_incr_comp_dir = old_incr_comp_session_dir.join(saved_path); debug!( "copying preexisting module `{}` from {:?} to {}", module.name, @@ -988,6 +1008,7 @@ fn do_fat_lto( fn do_thin_lto( cgcx: &CodegenContext, prof: &SelfProfilerRef, + incr_comp_session: Option, shared_emitter: SharedEmitter, tm_factory: TargetMachineFactoryFn, exported_symbols_for_lto: &[String], @@ -1031,6 +1052,7 @@ fn do_thin_lto( for (i, (work, cost)) in generate_thin_lto_work::( cgcx, prof, + incr_comp_session.as_ref(), dcx, &exported_symbols_for_lto, &each_linked_rlib_for_lto, @@ -1080,6 +1102,12 @@ fn do_thin_lto( spawn_thin_lto_work( &cgcx, prof, + incr_comp_session + .as_ref() + .and_then(|incr_comp_session| { + incr_comp_session.old_session_directory.as_deref() + }) + .map(ToOwned::to_owned), shared_emitter.clone(), Arc::clone(&tm_factory), coordinator_send.clone(), @@ -1213,6 +1241,8 @@ fn start_executing_work( ) -> thread::JoinHandle, ()>> { let sess = tcx.sess; let prof = sess.prof.clone(); + let incr_comp_session = + tcx.incr_comp_session.map(|incr_comp_session| incr_comp_session.borrow()); // Compute the set of symbols we need to retain when doing thin local LTO (if we need to) let exported_symbols_for_lto = @@ -1265,15 +1295,6 @@ fn start_executing_work( time_trace: sess.opts.unstable_opts.llvm_time_trace, remark: sess.opts.cg.remark.clone(), remark_dir, - 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.new_session_directory).to_owned()), output_filenames: Arc::clone(tcx.output_filenames(())), module_config: regular_config, opt_level, @@ -1512,6 +1533,7 @@ fn start_executing_work( spawn_work( &cgcx, &prof, + incr_comp_session.as_ref(), shared_emitter.clone(), coordinator_send.clone(), &mut llvm_start_time, @@ -1538,6 +1560,7 @@ fn start_executing_work( spawn_work( &cgcx, &prof, + incr_comp_session.as_ref(), shared_emitter.clone(), coordinator_send.clone(), &mut llvm_start_time, @@ -1582,6 +1605,7 @@ fn start_executing_work( spawn_work( &cgcx, &prof, + incr_comp_session.as_ref(), shared_emitter.clone(), coordinator_send.clone(), &mut llvm_start_time, @@ -1685,10 +1709,10 @@ fn start_executing_work( assert!(needs_thin_lto.is_empty()); needs_fat_lto.push(fat_lto_input); } - Ok(WorkItemResult::NeedsThinLto(name, thin_buffer)) => { + Ok(WorkItemResult::NeedsThinLto(wp, thin_buffer)) => { assert!(needs_fat_lto.is_empty()); needs_thin_lto.push(ThinLtoInput::Red { - name, + wp, buffer: SerializedModule::Local(thin_buffer), }); } @@ -1734,7 +1758,7 @@ fn start_executing_work( } for (bitcode_path, wp) in lto_import_only_modules { - needs_fat_lto.push(FatLtoInput::Serialized { name: wp.cgu_name, bitcode_path }) + needs_fat_lto.push(FatLtoInput::Serialized { wp, bitcode_path }) } return Ok(MaybeLtoModules::FatLto { cgcx, needs_fat_lto }); @@ -1750,6 +1774,7 @@ fn start_executing_work( compiled_modules.extend(do_thin_lto::( &cgcx, &prof, + incr_comp_session, shared_emitter.clone(), tm_factory, &exported_symbols_for_lto, @@ -1761,7 +1786,10 @@ fn start_executing_work( if let Some(allocator_module) = allocator_module.take() { let thin_buffer = B::serialize_module(allocator_module.module_llvm, true); needs_thin_lto.push(ThinLtoInput::Red { - name: allocator_module.name, + wp: WorkProduct { + cgu_name: allocator_module.name, + saved_files: UnordMap::default(), + }, buffer: SerializedModule::Local(thin_buffer), }); } @@ -1847,6 +1875,7 @@ pub(crate) struct WorkerFatalError; fn spawn_work<'a, B: WriteBackendMethods>( cgcx: &CodegenContext, prof: &'a SelfProfilerRef, + incr_comp_session: Option<&BorrowedIncrCompSession>, shared_emitter: SharedEmitter, coordinator_send: Sender>, llvm_start_time: &mut Option>, @@ -1857,6 +1886,11 @@ fn spawn_work<'a, B: WriteBackendMethods>( *llvm_start_time = Some(prof.verbose_generic_activity("LLVM_passes")); } + let old_incr_comp_session_dir = incr_comp_session + .and_then(|incr_comp_session| incr_comp_session.old_session_directory.clone()); + let new_incr_comp_session_dir = + incr_comp_session.map(|incr_comp_session| incr_comp_session.new_session_directory.clone()); + let cgcx = cgcx.clone(); let prof = prof.clone(); @@ -1865,10 +1899,22 @@ fn spawn_work<'a, B: WriteBackendMethods>( let _profiler = if cgcx.time_trace { B::thread_profiler() } else { Box::new(()) }; let result = std::panic::catch_unwind(AssertUnwindSafe(|| match work { - WorkItem::Optimize(m) => execute_optimize_work_item(&cgcx, &prof, shared_emitter, m), - WorkItem::CopyPostLtoArtifacts(m) => WorkItemResult::Finished( - execute_copy_from_cache_work_item(&cgcx, &prof, shared_emitter, m), + WorkItem::Optimize(m) => execute_optimize_work_item( + &cgcx, + &prof, + new_incr_comp_session_dir.as_deref(), + shared_emitter, + m, ), + WorkItem::CopyPostLtoArtifacts(m) => { + WorkItemResult::Finished(execute_copy_from_cache_work_item( + &cgcx, + &prof, + old_incr_comp_session_dir.as_deref().unwrap(), + shared_emitter, + m, + )) + } })); let msg = match result { @@ -1895,6 +1941,7 @@ fn spawn_work<'a, B: WriteBackendMethods>( fn spawn_thin_lto_work( cgcx: &CodegenContext, prof: &SelfProfilerRef, + old_incr_comp_session_dir: Option, shared_emitter: SharedEmitter, tm_factory: TargetMachineFactoryFn, coordinator_send: Sender, @@ -1909,9 +1956,13 @@ fn spawn_thin_lto_work( let _profiler = if cgcx.time_trace { B::thread_profiler() } else { Box::new(()) }; let result = std::panic::catch_unwind(AssertUnwindSafe(|| match work { - ThinLtoWorkItem::CopyPostLtoArtifacts(m) => { - execute_copy_from_cache_work_item(&cgcx, &prof, shared_emitter, m) - } + ThinLtoWorkItem::CopyPostLtoArtifacts(m) => execute_copy_from_cache_work_item( + &cgcx, + &prof, + old_incr_comp_session_dir.as_deref().unwrap(), + shared_emitter, + m, + ), ThinLtoWorkItem::ThinLto(m) => { let _timer = prof.generic_activity_with_arg("codegen_module_perform_lto", m.name()); B::optimize_and_codegen_thin(&cgcx, &prof, &shared_emitter, tm_factory, m) @@ -2111,34 +2162,58 @@ impl OngoingCodegen { let (shared_emitter, shared_emitter_main) = SharedEmitter::new(); // Catch fatal errors to ensure shared_emitter_main.check() can emit the actual diagnostics - let compiled_modules = catch_fatal_errors(|| match maybe_lto_modules { + let compilation_output = catch_fatal_errors(|| match maybe_lto_modules { MaybeLtoModules::NoLto(compiled_modules) => { drop(shared_emitter); - compiled_modules + + let work_products = copy_all_cgu_workproducts_to_incr_comp_cache_dir( + sess, + incr_comp_session, + &compiled_modules, + ); + + (compiled_modules, work_products) } MaybeLtoModules::FatLto { cgcx, needs_fat_lto } => { let tm_factory = self.backend.target_machine_factory(sess, cgcx.opt_level); - CompiledModules { - modules: vec![do_fat_lto( - sess, - &cgcx, - shared_emitter, - tm_factory, - &crate_info.exported_symbols_for_lto, - &crate_info.each_linked_rlib_file_for_lto, - needs_fat_lto, - )], - allocator_module: None, + let mut work_products = WorkProductMap::default(); + if sess.opts.incremental.is_some() { + for module in &needs_fat_lto { + match module { + FatLtoInput::Serialized { wp, bitcode_path: _ } => { + work_products + .insert(WorkProductId::from_cgu_name(&wp.cgu_name), wp.clone()); + } + FatLtoInput::InMemory(_) => {} + } + } } + + ( + CompiledModules { + modules: vec![do_fat_lto( + sess, + &cgcx, + shared_emitter, + tm_factory, + &crate_info.exported_symbols_for_lto, + &crate_info.each_linked_rlib_file_for_lto, + needs_fat_lto, + )], + allocator_module: None, + }, + work_products, + ) } MaybeLtoModules::ThinLto { cgcx, needs_thin_lto } => { let tm_factory = self.backend.target_machine_factory(sess, cgcx.opt_level); - CompiledModules { + let compiled_modules = CompiledModules { modules: do_thin_lto::( &cgcx, &sess.prof, + incr_comp_session.map(|incr_comp_session| incr_comp_session.borrow()), shared_emitter, tm_factory, &crate_info.exported_symbols_for_lto, @@ -2147,7 +2222,17 @@ impl OngoingCodegen { sess.opts.recommended_stack_size, ), allocator_module: None, - } + }; + + // FIXME include pre-LTO bitcode in workproduct tracking + // FIXME add separate incr comp session for post-LTO outputs to use during link step + let work_products = copy_all_cgu_workproducts_to_incr_comp_cache_dir( + sess, + incr_comp_session, + &compiled_modules, + ); + + (compiled_modules, work_products) } }); @@ -2155,19 +2240,14 @@ impl OngoingCodegen { sess.dcx().abort_if_errors(); - let mut compiled_modules = - compiled_modules.expect("fatal error emitted but not sent to SharedEmitter"); + let (mut compiled_modules, work_products) = + compilation_output.expect("fatal error emitted but not sent to SharedEmitter"); // Regardless of what order these modules completed in, report them to // the backend in the same order every time to ensure that we're handing // out deterministic results. compiled_modules.modules.sort_by(|a, b| a.name.cmp(&b.name)); - let work_products = copy_all_cgu_workproducts_to_incr_comp_cache_dir( - sess, - incr_comp_session, - &compiled_modules, - ); produce_final_output_artifacts(sess, &compiled_modules, &self.output_filenames); (compiled_modules, work_products) diff --git a/compiler/rustc_codegen_ssa/src/traits/backend.rs b/compiler/rustc_codegen_ssa/src/traits/backend.rs index e06ac36fd758a..bf5d67c213573 100644 --- a/compiler/rustc_codegen_ssa/src/traits/backend.rs +++ b/compiler/rustc_codegen_ssa/src/traits/backend.rs @@ -120,14 +120,6 @@ pub trait CodegenBackend { crate_info: &CrateInfo, ) -> (CompiledModules, WorkProductMap); - fn print_pass_timings(&self) {} - - fn print_statistics(&self) {} - - fn print_statistics_json(&self) -> String { - String::new() - } - /// This is called on the returned [`CompiledModules`] from [`join_codegen`](Self::join_codegen). fn link( &self, diff --git a/compiler/rustc_codegen_ssa/src/traits/write.rs b/compiler/rustc_codegen_ssa/src/traits/write.rs index bb63e189d20d3..4c0df97a41985 100644 --- a/compiler/rustc_codegen_ssa/src/traits/write.rs +++ b/compiler/rustc_codegen_ssa/src/traits/write.rs @@ -5,7 +5,7 @@ use std::path::PathBuf; use rustc_data_structures::profiling::SelfProfilerRef; use rustc_errors::DiagCtxtHandle; use rustc_middle::dep_graph::WorkProduct; -use rustc_session::{Session, config}; +use rustc_session::{BorrowedIncrCompSession, Session, config}; use crate::back::lto::ThinModule; use crate::back::write::{ @@ -50,6 +50,7 @@ pub trait WriteBackendMethods: Clone + 'static { fn run_thin_lto( cgcx: &CodegenContext, prof: &SelfProfilerRef, + incr_comp_session: Option<&BorrowedIncrCompSession>, dcx: DiagCtxtHandle<'_>, exported_symbols_for_lto: &[String], each_linked_rlib_for_lto: &[PathBuf], diff --git a/compiler/rustc_incremental/src/persist/fs.rs b/compiler/rustc_incremental/src/persist/fs.rs index 3f896584eaef2..038bddaa021d2 100644 --- a/compiler/rustc_incremental/src/persist/fs.rs +++ b/compiler/rustc_incremental/src/persist/fs.rs @@ -268,7 +268,7 @@ pub(crate) fn prepare_session_directory( None }; - IncrCompSession { old_session_directory, new_session_directory } + IncrCompSession::new(old_session_directory, new_session_directory) } /// This function finalizes and thus 'publishes' the session directory by diff --git a/compiler/rustc_interface/src/queries.rs b/compiler/rustc_interface/src/queries.rs index 759297bc69592..94cc1484586c8 100644 --- a/compiler/rustc_interface/src/queries.rs +++ b/compiler/rustc_interface/src/queries.rs @@ -67,30 +67,6 @@ impl Linker { } }); - if sess.codegen_units().as_usize() == 1 && sess.opts.unstable_opts.time_llvm_passes { - codegen_backend.print_pass_timings() - } - - if sess.print_llvm_stats() { - codegen_backend.print_statistics() - } - - if let Some(out_path) = sess.print_llvm_stats_json() { - let llvm_stats_json = codegen_backend.print_statistics_json(); - - if !llvm_stats_json.is_empty() { - if let Err(e) = std::fs::write(&out_path, llvm_stats_json) { - sess.dcx().err(format!("failed to write stats to {}: {}", out_path, e)); - } - } else { - sess.dcx().warn(format!( - "requested to print LLVM statistics to JSON file {}, but the codegen backend \ - did not provide any statistics", - out_path, - )); - } - } - sess.timings.end_section(sess.dcx(), TimingSection::Codegen); if sess.opts.incremental.is_some() diff --git a/compiler/rustc_middle/src/dep_graph/graph.rs b/compiler/rustc_middle/src/dep_graph/graph.rs index dcb5775f20595..f93b80cc6c95c 100644 --- a/compiler/rustc_middle/src/dep_graph/graph.rs +++ b/compiler/rustc_middle/src/dep_graph/graph.rs @@ -861,12 +861,6 @@ impl DepGraph { self.data.as_ref().and_then(|data| data.previous_work_products.get(v).cloned()) } - /// Access the map of work-products created during the cached run. Only - /// used during saving of the dep-graph. - pub fn previous_work_products(&self) -> &WorkProductMap { - &self.data.as_ref().unwrap().previous_work_products - } - pub fn debug_was_loaded_from_disk(&self, dep_node: DepNode) -> bool { self.data.as_ref().unwrap().debug_loaded_from_disk.lock().contains(&dep_node) } diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 1fe0fbdd2b6cd..781109d6c8658 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -2861,12 +2861,14 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { } fn resolve_item(&mut self, item: &'ast Item) { + let mut mod_inner_attrs_start = None; match item.kind { ItemKind::Mod(..) => { // We only handle outer doc comments for modules here. let attrs = if let Some(pos) = item.attrs.iter().position(|a| { a.doc_resolution_scope().is_some_and(|style| style == AttrStyle::Inner) }) { + mod_inner_attrs_start = Some(pos); &item.attrs[..pos] } else { &item.attrs @@ -2972,9 +2974,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { this.with_rib(TypeNS, RibKind::Module(module.expect_local()), |this| { // Outer doc comments were already handled above, now we handle // inner doc comments. - let attrs = if let Some(pos) = item.attrs.iter().position(|a| { - a.doc_resolution_scope().is_some_and(|style| style == AttrStyle::Inner) - }) { + let attrs = if let Some(pos) = mod_inner_attrs_start { &item.attrs[pos..] } else { &[] diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index f068aaa584dce..d86ee5e5a4898 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -1867,6 +1867,45 @@ pub struct IncrCompSession { /// The directory to which cached data for the current session can be /// written to. pub new_session_directory: flock::LockedDir, + borrows: Arc<()>, +} + +impl IncrCompSession { + pub fn new( + old_session_directory: Option, + new_session_directory: flock::LockedDir, + ) -> Self { + IncrCompSession { old_session_directory, new_session_directory, borrows: Arc::new(()) } + } + + pub fn borrow(&self) -> BorrowedIncrCompSession { + BorrowedIncrCompSession { + old_session_directory: self.old_session_directory.as_deref().map(ToOwned::to_owned), + new_session_directory: (&*self.new_session_directory).to_owned(), + _borrows: Arc::clone(&self.borrows), + } + } +} + +impl Drop for IncrCompSession { + fn drop(&mut self) { + // Check that there are no worker threads remaining that use the incr + // comp session before we unlock the old and new session dir. If there + // does exist a worker thread, there is not much we can do, but at + // least we will unconditionally complain rather than the worker thread + // sometimes crashing depending on what other rustc instances run. + assert!( + Arc::strong_count(&self.borrows) == 1, + "Incr comp session dropped while there are still references", + ); + } +} + +/// A runtime tracked borrow of the incr comp session. Can be sent to worker threads. +pub struct BorrowedIncrCompSession { + pub old_session_directory: Option, + pub new_session_directory: PathBuf, + _borrows: Arc<()>, } /// A wrapper around an [`DiagCtxt`] that is used for early error emissions. diff --git a/compiler/rustc_span/src/span_encoding.rs b/compiler/rustc_span/src/span_encoding.rs index af6f219b4e58e..cc67f34571b3b 100644 --- a/compiler/rustc_span/src/span_encoding.rs +++ b/compiler/rustc_span/src/span_encoding.rs @@ -13,69 +13,79 @@ use crate::{BytePos, SPAN_TRACK, SpanData}; /// A compressed span. /// -/// [`SpanData`] is 16 bytes, which is too big to stick everywhere. `Span` only -/// takes up 8 bytes, with less space for the length, parent and context. The -/// vast majority (99.9%+) of `SpanData` instances can be made to fit within -/// those 8 bytes. Any `SpanData` whose fields don't fit into a `Span` are -/// stored in a separate interner table, and the `Span` will index into that -/// table. Interning is rare enough that the cost is low, but common enough -/// that the code is exercised regularly. +/// [`SpanData`] is 16 bytes, which is too big to stick everywhere. `Span` is +/// only 8 bytes. A large majority of `SpanData` instances can be made to fit +/// within those 8 bytes. Any `SpanData` whose fields don't fit into a `Span` +/// are stored in a separate interner table, and the `Span` will index into +/// that table. /// /// An earlier version of this code used only 4 bytes for `Span`, but that was -/// slower because only 80--90% of spans could be stored inline (even less in -/// very large crates) and so the interner was used a lot more. That version of -/// the code also predated the storage of parents. +/// slower because many fewer spans could be stored inline and the interner was +/// used a lot more. That version of the code also predated the storage of +/// parents. +/// +/// Experiments with uncompressed spans yielded worse performance in most cases +/// because memory usage and cache miss rates are significantly higher without +/// compression. /// /// There are four different span forms. /// -/// Inline-context format (requires non-huge length, non-huge context, and no parent): -/// - `span.lo_or_index == span_data.lo` -/// - `span.len_with_tag_or_marker == len == span_data.hi - span_data.lo` (must be `<= MAX_LEN`) -/// - `span.ctxt_or_parent_or_marker == span_data.ctxt` (must be `<= MAX_CTXT`) +/// Inline-context format (requires ~15-bit length, ~16-bit context, and no parent): +/// - `span.lo_or_index` holds `span_data.lo` +/// - `span.len_with_tag_or_marker` holds `span_data.hi - span_data.lo` (must be `<= MAX_LEN`) +/// - `span.ctxt_or_parent_or_marker` holds `span_data.ctxt` (must be `<= MAX_CTXT_OR_PARENT`) /// -/// Inline-parent format (requires non-huge length, root context, and non-huge parent): -/// - `span.lo_or_index == span_data.lo` -/// - `span.len_with_tag_or_marker & !PARENT_TAG == len == span_data.hi - span_data.lo` -/// (must be `<= MAX_LEN`) -/// - `span.len_with_tag_or_marker` has top bit (`PARENT_TAG`) set -/// - `span.ctxt_or_parent_or_marker == span_data.parent` (must be `<= MAX_CTXT`) +/// Inline-parent format (requires ~15-bit length, root context, and ~16-bit parent): +/// - `span.lo_or_index` holds `span_data.lo` +/// - `span.len_with_tag_or_marker` holds `PARENT_TAG | (span_data.hi - span_data.lo)` +/// (the len part must be `<= MAX_LEN`) +/// - `span.ctxt_or_parent_or_marker` holds `span_data.parent` (must be `<= MAX_CTXT_OR_PARENT`) /// -/// Partially-interned format (requires non-huge context): -/// - `span.lo_or_index == index` (indexes into the interner table) -/// - `span.len_with_tag_or_marker == BASE_LEN_INTERNED_MARKER` -/// - `span.ctxt_or_parent_or_marker == span_data.ctxt` (must be `<= MAX_CTXT`) +/// Partially-interned format (requires ~16-bit context): +/// - `span.lo_or_index` holds the index into the interner table +/// - `span.len_with_tag_or_marker` is `LEN_INTERNED_MARKER` (all 1s) +/// - `span.ctxt_or_parent_or_marker` holds `span_data.ctxt` (must be `<= MAX_CTXT_OR_PARENT`) +/// - Requires looking in the interning table for lo and length, but the +/// context is stored inline as well as interned because context lookups are +/// often done in isolation. /// /// Fully-interned format (all cases not covered above): -/// - `span.lo_or_index == index` (indexes into the interner table) -/// - `span.len_with_tag_or_marker == BASE_LEN_INTERNED_MARKER` -/// - `span.ctxt_or_parent_or_marker == CTXT_INTERNED_MARKER` +/// - `span.lo_or_index` holds the index into the interner table +/// - `span.len_with_tag_or_marker` is `LEN_INTERNED_MARKER` (all 1s) +/// - `span.ctxt_or_parent_or_marker` is `CTXT_INTERNED_MARKER` (all 1s) /// -/// The partially-interned form requires looking in the interning table for -/// lo and length, but the context is stored inline as well as interned. -/// This is useful because context lookups are often done in isolation, and -/// inline lookups are quicker. +/// In short (see `match_span_kind!` for the code version of this): +/// - The absence/presence of `LEN_INTERNED_MARKER` in +/// `len_with_tag_or_marker` distinguishes the inline forms from the interned +/// forms. +/// - The absence/presence of the `PARENT_TAG` bit in +/// `len_with_tag_or_marker` distinguishes inline-context from inline-parent. +/// - The absence/presence of `CTXT_INTERNED_MARKER` in +/// `ctxt_or_parent_or_marker` distinguishes partially-interned from +/// fully-interned. /// /// Notes about the choice of field sizes: +/// /// - `lo` is 32 bits in both `Span` and `SpanData`, which means that `lo` /// values never cause interning. The number of bits needed for `lo` /// depends on the crate size. 32 bits allows up to 4 GiB of code in a crate. /// Having no compression on this field means there is no performance cliff /// if a crate exceeds a particular size. +/// /// - `len` is ~15 bits in `Span` (a u16, minus 1 bit for PARENT_TAG) and 32 /// bits in `SpanData`, which means that large `len` values will cause /// interning. The number of bits needed for `len` does not depend on the /// crate size. The most common numbers of bits for `len` are from 0 to 7, /// with a peak usually at 3 or 4, and then it drops off quickly from 8 -/// onwards. 15 bits is enough for 99.99%+ of cases, but larger values +/// onwards. 15 bits is enough for 99.9%+ of cases, but larger values /// (sometimes 20+ bits) might occur dozens of times in a typical crate. +/// /// - `ctxt_or_parent_or_marker` is 16 bits in `Span` and two 32 bit fields in -/// `SpanData`, which means intering will happen if `ctxt` is large, if +/// `SpanData`, which means interning will happen if `ctxt` is large, if /// `parent` is large, or if both values are non-zero. The number of bits /// needed for `ctxt` values depend partly on the crate size and partly on -/// the form of the code. No crates in `rustc-perf` need more than 15 bits -/// for `ctxt_or_parent_or_marker`, but larger crates might need more than 16 -/// bits. The number of bits needed for `parent` hasn't been measured, -/// because `parent` isn't currently used by default. +/// the form of the code. No crates in `rustc-perf` need more than 16 bits +/// for `ctxt`, but larger crates might. The same is true for `parent`. /// /// In order to reliably use parented spans in incremental compilation, /// accesses to `lo` and `hi` must introduce a dependency to the parent definition's span. @@ -181,7 +191,7 @@ impl PartiallyInterned { #[inline] fn span(index: u32, ctxt: u16) -> Span { let (lo_or_index, len_with_tag_or_marker, ctxt_or_parent_or_marker) = - (index, BASE_LEN_INTERNED_MARKER, ctxt); + (index, LEN_INTERNED_MARKER, ctxt); Span { lo_or_index, len_with_tag_or_marker, ctxt_or_parent_or_marker } } #[inline] @@ -198,7 +208,7 @@ impl Interned { #[inline] fn span(index: u32) -> Span { let (lo_or_index, len_with_tag_or_marker, ctxt_or_parent_or_marker) = - (index, BASE_LEN_INTERNED_MARKER, CTXT_INTERNED_MARKER); + (index, LEN_INTERNED_MARKER, CTXT_INTERNED_MARKER); Span { lo_or_index, len_with_tag_or_marker, ctxt_or_parent_or_marker } } #[inline] @@ -218,7 +228,7 @@ macro_rules! match_span_kind { PartiallyInterned($span3:ident) => $arm3:expr, Interned($span4:ident) => $arm4:expr, ) => { - if $span.len_with_tag_or_marker != BASE_LEN_INTERNED_MARKER { + if $span.len_with_tag_or_marker != LEN_INTERNED_MARKER { if $span.len_with_tag_or_marker & PARENT_TAG == 0 { // Inline-context format. let $span1 = InlineCtxt::from_span($span); @@ -241,11 +251,14 @@ macro_rules! match_span_kind { } // `MAX_LEN` is chosen so that `PARENT_TAG | MAX_LEN` is distinct from -// `BASE_LEN_INTERNED_MARKER`. (If `MAX_LEN` was 1 higher, this wouldn't be true.) +// `LEN_INTERNED_MARKER`. (If `MAX_LEN` was 1 higher, this wouldn't be true.) const MAX_LEN: u32 = 0b0111_1111_1111_1110; -const MAX_CTXT: u32 = 0b0111_1111_1111_1110; const PARENT_TAG: u16 = 0b1000_0000_0000_0000; -const BASE_LEN_INTERNED_MARKER: u16 = 0b1111_1111_1111_1111; +const LEN_INTERNED_MARKER: u16 = 0b1111_1111_1111_1111; + +// `MAX_CTXT_OR_PARENT` is chosen so it's as big as possible while not equal to +// `CTXT_INTERNED_MARKER`. +const MAX_CTXT_OR_PARENT: u32 = 0b1111_1111_1111_1110; const CTXT_INTERNED_MARKER: u16 = 0b1111_1111_1111_1111; /// The dummy span has zero position, length, and context, and no parent. @@ -266,12 +279,12 @@ impl Span { // Small len and ctxt may enable one of fully inline formats (or may not). let (len, ctxt32) = (hi.0 - lo.0, ctxt.as_u32()); - if len <= MAX_LEN && ctxt32 <= MAX_CTXT { + if len <= MAX_LEN && ctxt32 <= MAX_CTXT_OR_PARENT { match parent { None => return InlineCtxt::span(lo.0, len as u16, ctxt32 as u16), Some(parent) => { let parent32 = parent.local_def_index.as_u32(); - if ctxt32 == 0 && parent32 <= MAX_CTXT { + if ctxt32 == 0 && parent32 <= MAX_CTXT_OR_PARENT { return InlineParent::span(lo.0, len as u16, parent32 as u16); } } @@ -282,7 +295,7 @@ impl Span { let index = |ctxt| { with_span_interner(|interner| interner.intern(&SpanData { lo, hi, ctxt, parent })) }; - if ctxt32 <= MAX_CTXT { + if ctxt32 <= MAX_CTXT_OR_PARENT { // Interned ctxt should never be read, so it can use any value. PartiallyInterned::span(index(SyntaxContext::from_u32(u32::MAX)), ctxt32 as u16) } else { @@ -335,7 +348,7 @@ impl Span { /// Returns `true` if this is a dummy span with any hygienic context. #[inline] pub fn is_dummy(self) -> bool { - if self.len_with_tag_or_marker != BASE_LEN_INTERNED_MARKER { + if self.len_with_tag_or_marker != LEN_INTERNED_MARKER { // Inline-context or inline-parent format. let lo = self.lo_or_index; let len = (self.len_with_tag_or_marker & !PARENT_TAG) as u32; @@ -358,7 +371,7 @@ impl Span { // so it makes sense to micro-optimize it to avoid `span.data()` and `Span::new()`. let new_ctxt = map(SyntaxContext::from_u16(span.ctxt)); let new_ctxt32 = new_ctxt.as_u32(); - return if new_ctxt32 <= MAX_CTXT { + return if new_ctxt32 <= MAX_CTXT_OR_PARENT { // Any small new context including zero will preserve the format. InlineCtxt::span(span.lo, span.len, new_ctxt32 as u16) } else { @@ -399,9 +412,10 @@ impl Span { pub fn eq_ctxt(self, other: Span) -> bool { match (self.inline_ctxt(), other.inline_ctxt()) { (Ok(ctxt1), Ok(ctxt2)) => ctxt1 == ctxt2, - // If `inline_ctxt` returns `Ok` the context is <= MAX_CTXT. - // If it returns `Err` the span is fully interned and the context is > MAX_CTXT. - // As these do not overlap an `Ok` and `Err` result cannot have an equal context. + // If `inline_ctxt` returns `Ok` the context is <= MAX_CTXT_OR_PARENT. + // If it returns `Err` the span is fully interned and the context + // is > MAX_CTXT_OR_PARENT. As these do not overlap an `Ok` and `Err` result cannot + // have an equal context. (Ok(_), Err(_)) | (Err(_), Ok(_)) => false, (Err(index1), Err(index2)) => with_span_interner(|interner| { interner.spans[index1].ctxt == interner.spans[index2].ctxt @@ -421,7 +435,7 @@ impl Span { None => return self, Some(parent) => { let parent32 = parent.local_def_index.as_u32(); - if span.ctxt == 0 && parent32 <= MAX_CTXT { + if span.ctxt == 0 && parent32 <= MAX_CTXT_OR_PARENT { return InlineParent::span(span.lo, span.len, parent32 as u16); } } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index a003f20e3512d..9cdb069f61747 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1000,6 +1000,7 @@ symbols! { fields, file, final_associated_functions, + float_mul_add_relaxed, float_to_int_unchecked, floorf16, floorf32, diff --git a/compiler/rustc_trait_selection/src/solve/delegate.rs b/compiler/rustc_trait_selection/src/solve/delegate.rs index 8f2d496f95b25..916a5682cb0c3 100644 --- a/compiler/rustc_trait_selection/src/solve/delegate.rs +++ b/compiler/rustc_trait_selection/src/solve/delegate.rs @@ -229,6 +229,10 @@ impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate< } let ty = self.deeply_resolve_ignoring_regions(outlives.0); + if ty.has_non_rigid_aliases() { + return Outcome::NoFastPath; + } + let mut infer_collector = CollectNonRegionInfer { infers: Default::default(), visited: Default::default(), @@ -247,10 +251,6 @@ impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate< ); } - if ty.has_non_rigid_aliases() { - return Outcome::NoFastPath; - } - self.0.register_type_outlives_constraint( outlives.0, outlives.1, diff --git a/compiler/rustc_trait_selection/src/solve/fulfill.rs b/compiler/rustc_trait_selection/src/solve/fulfill.rs index 7d3c0a4a6c1f1..5e834c8c4059d 100644 --- a/compiler/rustc_trait_selection/src/solve/fulfill.rs +++ b/compiler/rustc_trait_selection/src/solve/fulfill.rs @@ -18,7 +18,7 @@ use self::derive_errors::*; use super::Certainty; use super::delegate::SolverDelegate; use crate::error_reporting::InferCtxtErrorExt; -use crate::traits::{FulfillmentError, FulfillmentErrorCode, ScrubbedTraitError}; +use crate::traits::{FulfillmentError, ScrubbedTraitError}; mod derive_errors; @@ -157,8 +157,7 @@ where // the other case. TraitErrors::NoErrors } else { - let errors = collect_remaining_errors_impl(self, infcx); - TraitErrors::from_iter(errors.into_iter()) + TraitErrors::HasErrors(collect_remaining_errors_impl(self, infcx)) } } @@ -367,26 +366,14 @@ where cx.obligations .pending .drain(..) - .filter_map(|(obligation, _)| { - try_ambiguity_error_for_stalled(infcx, obligation).map(NextSolverError::Ambiguity) - }) + .map(|(obligation, _)| NextSolverError::Ambiguity(obligation)) .map(|e| E::from_solver_error(infcx, e)) .collect() } -// We evaluate stalled obligations while collecting remaining errors because a -// previously ambiguous goal may have become successful. In that case we emit a -// delayed bug instead of producing a fulfillment error. Store the diagnostic -// information here so error conversion does not reevaluate the goal. -pub struct NextSolverAmbiguityError<'tcx> { - root_obligation: PredicateObligation<'tcx>, - code: FulfillmentErrorCode<'tcx>, - refine_obligation: bool, -} - pub enum NextSolverError<'tcx> { TrueError(PredicateObligation<'tcx>), - Ambiguity(NextSolverAmbiguityError<'tcx>), + Ambiguity(PredicateObligation<'tcx>), } impl<'tcx> FromSolverError<'tcx, NextSolverError<'tcx>> for FulfillmentError<'tcx> { @@ -395,8 +382,8 @@ impl<'tcx> FromSolverError<'tcx, NextSolverError<'tcx>> for FulfillmentError<'tc NextSolverError::TrueError(obligation) => { fulfillment_error_for_no_solution(infcx, obligation) } - NextSolverError::Ambiguity(ambiguity) => { - fulfillment_error_for_stalled(infcx, ambiguity) + NextSolverError::Ambiguity(obligation) => { + fulfillment_error_for_stalled(infcx, obligation) } } } diff --git a/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs b/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs index 5ccd7ff7ef55b..7df3fa41a1df2 100644 --- a/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs +++ b/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs @@ -14,7 +14,6 @@ use rustc_next_trait_solver::solve::{GoalEvaluation, MaybeInfo, SolverDelegateEv use rustc_span::{bug, span_bug}; use tracing::{instrument, trace}; -use super::NextSolverAmbiguityError; use crate::solve::delegate::SolverDelegate; use crate::solve::inspect::{self, InferCtxtProofTreeExt, ProofTreeVisitor}; use crate::solve::{Certainty, deeply_normalize_for_diagnostics}; @@ -84,25 +83,10 @@ pub(super) fn fulfillment_error_for_no_solution<'tcx>( } pub(super) fn fulfillment_error_for_stalled<'tcx>( - infcx: &InferCtxt<'tcx>, - ambiguity: NextSolverAmbiguityError<'tcx>, -) -> FulfillmentError<'tcx> { - let NextSolverAmbiguityError { root_obligation, code, refine_obligation } = ambiguity; - - let obligation = if refine_obligation { - find_best_leaf_obligation(infcx, &root_obligation, true) - } else { - root_obligation.clone() - }; - - FulfillmentError { obligation, code, root_obligation } -} - -pub(super) fn try_ambiguity_error_for_stalled<'tcx>( infcx: &InferCtxt<'tcx>, root_obligation: PredicateObligation<'tcx>, -) -> Option> { - let evaluation = infcx.probe(|_| { +) -> FulfillmentError<'tcx> { + let (code, refine_obligation) = infcx.probe(|_| { match <&SolverDelegate<'tcx>>::from(infcx).evaluate_root_goal( root_obligation.as_goal(), root_obligation.cause.span, @@ -116,7 +100,7 @@ pub(super) fn try_ambiguity_error_for_stalled<'tcx>( stalled_on_coroutines: _, }), .. - }) => Some((FulfillmentErrorCode::Ambiguity { overflow: None }, true)), + }) => (FulfillmentErrorCode::Ambiguity { overflow: None }, true), Ok(GoalEvaluation { certainty: Certainty::Maybe(MaybeInfo { @@ -126,7 +110,7 @@ pub(super) fn try_ambiguity_error_for_stalled<'tcx>( stalled_on_coroutines: _, }), .. - }) => Some(( + }) => ( FulfillmentErrorCode::Ambiguity { overflow: Some(suggest_increasing_limit) }, // Don't look into overflows because we treat overflows weirdly anyways. // We discard the inference constraints from overflowing goals, so @@ -135,17 +119,13 @@ pub(super) fn try_ambiguity_error_for_stalled<'tcx>( // // FIXME: We should probably just look into overflows here. false, - )), + ), Ok(GoalEvaluation { certainty: Certainty::Yes, .. }) => { - infcx.dcx().span_delayed_bug( - root_obligation.cause.span, - format!( - "did not expect successful goal when collecting ambiguity errors for `{:?}`", - infcx.deeply_resolve_ignoring_regions(root_obligation.predicate), - ), - ); - None - }, + // FIXME: We should ICE here. See the following links for details + // - + // - + (FulfillmentErrorCode::Ambiguity { overflow: None }, false) + } Err(_) => { span_bug!( root_obligation.cause.span, @@ -156,9 +136,15 @@ pub(super) fn try_ambiguity_error_for_stalled<'tcx>( } }); - let (code, refine_obligation) = evaluation?; - - Some(NextSolverAmbiguityError { root_obligation, code, refine_obligation }) + FulfillmentError { + obligation: if refine_obligation { + find_best_leaf_obligation(infcx, &root_obligation, true) + } else { + root_obligation.clone() + }, + code, + root_obligation, + } } #[instrument(level = "debug", skip(infcx), ret)] diff --git a/compiler/rustc_type_ir/src/binder.rs b/compiler/rustc_type_ir/src/binder.rs index 6722adfe24d5d..383e394c3a9e7 100644 --- a/compiler/rustc_type_ir/src/binder.rs +++ b/compiler/rustc_type_ir/src/binder.rs @@ -241,10 +241,10 @@ impl TypeVisitor for ValidateBoundVars { } fn visit_ty(&mut self, t: I::Ty) -> Self::Result { - if t.outer_exclusive_binder() < self.binder_index + if t.outer_exclusive_binder() <= self.binder_index || !self.visited.insert((self.binder_index, t)) { - return ControlFlow::Break(()); + return ControlFlow::Continue(()); } match t.kind() { ty::Bound(ty::BoundVarIndexKind::Bound(debruijn), bound_ty) @@ -263,8 +263,8 @@ impl TypeVisitor for ValidateBoundVars { } fn visit_const(&mut self, c: Const) -> Self::Result { - if c.outer_exclusive_binder() < self.binder_index { - return ControlFlow::Break(()); + if c.outer_exclusive_binder() <= self.binder_index { + return ControlFlow::Continue(()); } match c.kind() { ty::ConstKind::Bound(debruijn, bound_const) diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index f44da9840b8e5..01d852fd52b5f 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -1404,6 +1404,9 @@ pub const fn fmaf128(a: f128, b: f128, c: f128) -> f128; /// and add instructions. It is unspecified whether or not a fused operation /// is selected, and that may depend on optimization level and context, for /// example. +/// +/// The stabilized version of this intrinsic is +/// [`f16::mul_add_relaxed`](../../std/primitive.f16.html#method.mul_add_relaxed) #[inline] #[rustc_intrinsic] #[rustc_nounwind] @@ -1420,6 +1423,9 @@ pub const fn fmuladdf16(a: f16, b: f16, c: f16) -> f16 { /// and add instructions. It is unspecified whether or not a fused operation /// is selected, and that may depend on optimization level and context, for /// example. +/// +/// The stabilized version of this intrinsic is +/// [`f32::mul_add_relaxed`](../../std/primitive.f32.html#method.mul_add_relaxed) #[inline] #[rustc_intrinsic] #[rustc_nounwind] @@ -1436,6 +1442,9 @@ pub const fn fmuladdf32(a: f32, b: f32, c: f32) -> f32 { /// and add instructions. It is unspecified whether or not a fused operation /// is selected, and that may depend on optimization level and context, for /// example. +/// +/// The stabilized version of this intrinsic is +/// [`f64::mul_add_relaxed`](../../std/primitive.f64.html#method.mul_add_relaxed) #[inline] #[rustc_intrinsic] #[rustc_nounwind] @@ -1452,6 +1461,9 @@ pub const fn fmuladdf64(a: f64, b: f64, c: f64) -> f64 { /// and add instructions. It is unspecified whether or not a fused operation /// is selected, and that may depend on optimization level and context, for /// example. +/// +/// The stabilized version of this intrinsic is +/// [`f128::mul_add_relaxed`](../../std/primitive.f128.html#method.mul_add_relaxed) #[inline] #[rustc_intrinsic] #[rustc_nounwind] diff --git a/library/core/src/num/f128.rs b/library/core/src/num/f128.rs index e53e4df1ea43d..2096b2f4d6d15 100644 --- a/library/core/src/num/f128.rs +++ b/library/core/src/num/f128.rs @@ -594,7 +594,7 @@ impl f128 { /// conserved over arithmetic operations, the result of `is_sign_positive` on /// a NaN might produce an unexpected or non-portable result. See the [specification /// of NaN bit patterns](f32#nan-bit-patterns) for more info. Use `self.signum() == 1.0` - /// if you need fully portable behavior (will return NaN for all NaNs). + /// if you need fully portable behavior (evaluates to `false` for all NaNs). /// /// ``` /// #![feature(f128)] @@ -620,7 +620,7 @@ impl f128 { /// conserved over arithmetic operations, the result of `is_sign_negative` on /// a NaN might produce an unexpected or non-portable result. See the [specification /// of NaN bit patterns](f32#nan-bit-patterns) for more info. Use `self.signum() == -1.0` - /// if you need fully portable behavior (will return NaN for all NaNs). + /// if you need fully portable behavior (evaluates to `false` for all NaNs). /// /// ``` /// #![feature(f128)] @@ -1998,6 +1998,45 @@ impl f128 { intrinsics::fmaf128(self, a, b) } + /// Computes `(self * a) + b` with nondeterministic rounding. + /// + /// This is similar to [`mul_add`](Self::mul_add), but the intermediate + /// result may be rounded differently depending on the implementation. + /// The operation is either executed as a single fused multiply-add + /// instruction, or as separate multiply and add instructions. + /// + /// The choice of which one is used is unspecified and non-deterministic: + /// it may vary by target, optimization level, and surrounding code, and + /// even two invocations of this operation with the same inputs may + /// produce different results. + /// + /// # Examples + /// + /// ``` + /// #![feature(f128)] + /// #![feature(float_mul_add_relaxed)] + /// # #[cfg(any(miri, target_has_reliable_f128_math))] { // Miri uses softfloats, always works + /// + /// // When the fused and unfused operations round differently, either + /// // result may be returned: + /// // - 7.824090399073145653039910391751267e-37 is the fused result (one rounding) + /// // - 1.5046327690525280101999827676444745e-36 is the unfused result (two roundings) + /// let r = 0.1_f128.mul_add_relaxed(0.1_f128, -0.01_f128); + /// assert!( + /// r == 7.824090399073145653039910391751267e-37 + /// || r == 1.5046327690525280101999827676444745e-36 + /// ); + /// # } + /// ``` + #[inline] + #[rustc_allow_incoherent_impl] + #[doc(alias = "fmuladd")] + #[unstable(feature = "float_mul_add_relaxed", issue = "151770")] + #[must_use = "method returns a new number and does not mutate the original value"] + pub const fn mul_add_relaxed(self, a: f128, b: f128) -> f128 { + intrinsics::fmuladdf128(self, a, b) + } + /// Calculates Euclidean division, the matching method for `rem_euclid`. /// /// This computes the integer `n` such that diff --git a/library/core/src/num/f16.rs b/library/core/src/num/f16.rs index 4ad41bd6cfa4b..7332c0a586b8f 100644 --- a/library/core/src/num/f16.rs +++ b/library/core/src/num/f16.rs @@ -588,7 +588,7 @@ impl f16 { /// conserved over arithmetic operations, the result of `is_sign_positive` on /// a NaN might produce an unexpected or non-portable result. See the [specification /// of NaN bit patterns](f32#nan-bit-patterns) for more info. Use `self.signum() == 1.0` - /// if you need fully portable behavior (will return NaN for all NaNs). + /// if you need fully portable behavior (evaluates to `false` for all NaNs). /// /// ``` /// #![feature(f16)] @@ -616,7 +616,7 @@ impl f16 { /// conserved over arithmetic operations, the result of `is_sign_negative` on /// a NaN might produce an unexpected or non-portable result. See the [specification /// of NaN bit patterns](f32#nan-bit-patterns) for more info. Use `self.signum() == -1.0` - /// if you need fully portable behavior (will return NaN for all NaNs). + /// if you need fully portable behavior (evaluates to `false` for all NaNs). /// /// ``` /// #![feature(f16)] @@ -1984,6 +1984,42 @@ impl f16 { intrinsics::fmaf16(self, a, b) } + /// Computes `(self * a) + b` with nondeterministic rounding. + /// + /// This is similar to [`mul_add`](Self::mul_add), but the intermediate + /// result may be rounded differently depending on the implementation. + /// The operation is either executed as a single fused multiply-add + /// instruction, or as separate multiply and add instructions. + /// + /// The choice of which one is used is unspecified and non-deterministic: + /// it may vary by target, optimization level, and surrounding code, and + /// even two invocations of this operation with the same inputs may + /// produce different results. + /// + /// # Examples + /// + /// ``` + /// #![feature(f16)] + /// #![feature(float_mul_add_relaxed)] + /// # #[cfg(target_has_reliable_f16)] { + /// + /// // When the fused and unfused operations round differently, either + /// // result may be returned: + /// // - -7.03e-6 is the fused result (one rounding) + /// // - -7.6e-6 is the unfused result (two roundings) + /// let r = 0.1_f16.mul_add_relaxed(0.1_f16, -0.01_f16); + /// assert!(r == -7.03e-6 || r == -7.6e-6); + /// # } + /// ``` + #[inline] + #[rustc_allow_incoherent_impl] + #[doc(alias = "fmuladd")] + #[unstable(feature = "float_mul_add_relaxed", issue = "151770")] + #[must_use = "method returns a new number and does not mutate the original value"] + pub const fn mul_add_relaxed(self, a: f16, b: f16) -> f16 { + intrinsics::fmuladdf16(self, a, b) + } + /// Calculates Euclidean division, the matching method for `rem_euclid`. /// /// This computes the integer `n` such that diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs index 2dc3ddf31cac5..c98bff20cdbf9 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -811,7 +811,7 @@ impl f32 { /// conserved over arithmetic operations, the result of `is_sign_positive` on /// a NaN might produce an unexpected or non-portable result. See the [specification /// of NaN bit patterns](f32#nan-bit-patterns) for more info. Use `self.signum() == 1.0` - /// if you need fully portable behavior (will return NaN for all NaNs). + /// if you need fully portable behavior (evaluates to `false` for all NaNs). /// /// ``` /// let f = 7.0_f32; @@ -836,7 +836,7 @@ impl f32 { /// conserved over arithmetic operations, the result of `is_sign_negative` on /// a NaN might produce an unexpected or non-portable result. See the [specification /// of NaN bit patterns](f32#nan-bit-patterns) for more info. Use `self.signum() == -1.0` - /// if you need fully portable behavior (will return NaN for all NaNs). + /// if you need fully portable behavior (evaluates to `false` for all NaNs). /// /// ``` /// let f = 7.0f32; @@ -1880,6 +1880,47 @@ impl f32 { intrinsics::frem_algebraic(self, rhs) } + /// Computes `(self * a) + b` with nondeterministic rounding. + /// + /// This is similar to [`mul_add`], but the intermediate result may be + /// rounded differently depending on the implementation. The operation is + /// either executed as a single fused multiply-add instruction, or as + /// separate multiply and add instructions. + /// + /// The choice of which one is used is unspecified and non-deterministic: + /// it may vary by target, optimization level, and surrounding code, and + /// even two invocations of this operation with the same inputs may + /// produce different results. + /// + /// # Precision + /// + /// The result of this operation is not guaranteed: it is either the result + /// of [`mul_add`] (one rounding of the infinite-precision result) or of + /// `self * a + b` (two roundings, with an intermediate rounding of the + /// product). + /// + /// # Examples + /// + /// ``` + /// #![feature(float_mul_add_relaxed)] + /// + /// // When the fused and unfused operations round differently, either + /// // result may be returned: + /// // - 5.2154064e-10 is the fused result (one rounding) + /// // - 9.313226e-10 is the unfused result (two roundings) + /// let r = 0.1_f32.mul_add_relaxed(0.1_f32, -0.01_f32); + /// assert!(r == 5.2154064e-10 || r == 9.313226e-10); + /// ``` + /// + /// [`mul_add`]: ../std/primitive.f32.html#method.mul_add + #[must_use = "method returns a new number and does not mutate the original value"] + #[doc(alias = "fmuladd")] + #[unstable(feature = "float_mul_add_relaxed", issue = "151770")] + #[inline] + pub const fn mul_add_relaxed(self, a: f32, b: f32) -> f32 { + intrinsics::fmuladdf32(self, a, b) + } + /// Returns `self` if the value is not NaN, otherwise returns `replacement` /// if `self` is NaN. /// diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs index cd3cfe1bd4c60..3a51e49f94f1d 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -810,7 +810,7 @@ impl f64 { /// conserved over arithmetic operations, the result of `is_sign_positive` on /// a NaN might produce an unexpected or non-portable result. See the [specification /// of NaN bit patterns](f32#nan-bit-patterns) for more info. Use `self.signum() == 1.0` - /// if you need fully portable behavior (will return NaN for all NaNs). + /// if you need fully portable behavior (evaluates to `false` for all NaNs). /// /// ``` /// let f = 7.0_f64; @@ -835,7 +835,7 @@ impl f64 { /// conserved over arithmetic operations, the result of `is_sign_negative` on /// a NaN might produce an unexpected or non-portable result. See the [specification /// of NaN bit patterns](f32#nan-bit-patterns) for more info. Use `self.signum() == -1.0` - /// if you need fully portable behavior (will return NaN for all NaNs). + /// if you need fully portable behavior (evaluates to `false` for all NaNs). /// /// ``` /// let f = 7.0_f64; @@ -1858,6 +1858,50 @@ impl f64 { intrinsics::frem_algebraic(self, rhs) } + /// Computes `(self * a) + b` with nondeterministic rounding. + /// + /// This is similar to [`mul_add`], but the intermediate result may be + /// rounded differently depending on the implementation. The operation is + /// either executed as a single fused multiply-add instruction, or as + /// separate multiply and add instructions. + /// + /// The choice of which one is used is unspecified and non-deterministic: + /// it may vary by target, optimization level, and surrounding code, and + /// even two invocations of this operation with the same inputs may + /// produce different results. + /// + /// # Precision + /// + /// The result of this operation is not guaranteed: it is either the result + /// of [`mul_add`] (one rounding of the infinite-precision result) or of + /// `self * a + b` (two roundings, with an intermediate rounding of the + /// product). + /// + /// # Examples + /// + /// ``` + /// #![feature(float_mul_add_relaxed)] + /// + /// // When the fused and unfused operations round differently, either + /// // result may be returned: + /// // - 9.020562075079397e-19 is the fused result (one rounding) + /// // - 1.734723475976807e-18 is the unfused result (two roundings) + /// # // FIXME(#114479): on `i586`, x87 excess precision gives neither allowed result + /// # #[cfg(not(all(target_arch = "x86", not(target_feature = "sse2"))))] { + /// let r = 0.1_f64.mul_add_relaxed(0.1_f64, -0.01_f64); + /// assert!(r == 9.020562075079397e-19 || r == 1.734723475976807e-18); + /// # } + /// ``` + /// + /// [`mul_add`]: ../std/primitive.f64.html#method.mul_add + #[must_use = "method returns a new number and does not mutate the original value"] + #[doc(alias = "fmuladd")] + #[unstable(feature = "float_mul_add_relaxed", issue = "151770")] + #[inline] + pub const fn mul_add_relaxed(self, a: f64, b: f64) -> f64 { + intrinsics::fmuladdf64(self, a, b) + } + /// Returns `self` if the value is not NaN, otherwise returns `replacement` /// if `self` is NaN. /// diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index cc40390fa6200..6cbf442c658b1 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -1,7 +1,5 @@ #![allow(nonstandard_style)] #![allow(unsafe_op_in_unsafe_fn)] -// miri has some special hacks here that make things unused. -#![cfg_attr(miri, allow(unused))] #[cfg(test)] mod tests; @@ -988,7 +986,6 @@ impl Drop for DirStream { fn drop(&mut self) { // dirfd isn't supported everywhere #[cfg(not(any( - miri, target_os = "redox", target_os = "nto", target_os = "qnx", @@ -1030,16 +1027,13 @@ impl DirEntry { pub fn metadata(&self) -> io::Result { cfg_select! { // Use directory handle where possible - all( - any( - all(target_os = "linux", not(target_env = "musl")), - target_os = "android", - target_os = "fuchsia", - target_os = "hurd", - target_os = "illumos", - target_vendor = "apple", - ), - not(miri) // no dirfd on Miri + any( + all(target_os = "linux", not(target_env = "musl")), + target_os = "android", + target_os = "fuchsia", + target_os = "hurd", + target_os = "illumos", + target_vendor = "apple", ) => { let fd = cvt(unsafe { dirfd(self.dir.dirp.0) })?; diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index 97e7280da1e37..816126b5405b8 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -2675,7 +2675,7 @@ pub fn run_cargo( let (filenames_vec, crate_types) = match msg { CargoMessage::CompilerArtifact { filenames, - target: CargoTarget { crate_types }, + target: CargoTarget { crate_types, .. }, .. } => { let mut f: Vec = filenames.into_iter().map(|s| s.into_owned()).collect(); @@ -2882,12 +2882,14 @@ pub fn stream_cargo( status.success() } -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] pub struct CargoTarget<'a> { - crate_types: Vec>, + pub crate_types: Vec>, + #[serde(default)] + pub doc: bool, } -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] #[serde(tag = "reason", rename_all = "kebab-case")] pub enum CargoMessage<'a> { CompilerArtifact { filenames: Vec>, target: CargoTarget<'a> }, diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs index 7073b334c414d..d2cd8c77bc9db 100644 --- a/src/bootstrap/src/core/build_steps/dist.rs +++ b/src/bootstrap/src/core/build_steps/dist.rs @@ -23,7 +23,7 @@ use crate::core::backend::CodegenBackendKind; use crate::core::build_steps::compile::{ get_codegen_backend_file, libgccjit_path_relative_to_cg_dir, normalize_codegen_backend_name, }; -use crate::core::build_steps::doc::DocumentationFormat; +use crate::core::build_steps::doc::{CompilerWithTools, DocumentationFormat}; use crate::core::build_steps::gcc::GccTargetPair; use crate::core::build_steps::llvm::{ LLVM_CI_LINK_TYPE_PATH, LlvmBuildStatus, LlvmKind, get_llvm_build_status, @@ -185,7 +185,7 @@ impl CommandLineStep for JsonDocs { } } -/// Builds the `rustc-docs` installer component. +/// Builds the `rustc-docs` component. /// Apart from the documentation of the `rustc_*` crates, it also includes the documentation of /// various in-tree helper tools (bootstrap, build_helper, tidy), /// and also rustc_private tools like rustdoc, clippy, miri or rustfmt. @@ -214,11 +214,12 @@ impl CommandLineStep for RustcDocs { fn run(self, builder: &Builder<'_>) -> Self::Output { let target = self.target; - builder.run_default_doc_steps(); + let combined_docs = + builder.ensure(CompilerWithTools::for_stage(builder, builder.top_stage, self.target)); let mut tarball = Tarball::new(builder, "rustc-docs", &target.triple); tarball.set_product_name("Rustc Documentation"); - tarball.add_bulk_dir(builder.compiler_doc_out(target), "share/doc/rust/html/rustc-docs"); + tarball.add_bulk_dir(combined_docs, "share/doc/rust/html/rustc-docs"); tarball.generate() } } diff --git a/src/bootstrap/src/core/build_steps/doc.rs b/src/bootstrap/src/core/build_steps/doc.rs index 2c64d7915c392..34e9b354bddb2 100644 --- a/src/bootstrap/src/core/build_steps/doc.rs +++ b/src/bootstrap/src/core/build_steps/doc.rs @@ -7,11 +7,13 @@ //! Everything here is basically just a shim around calling either `rustbook` or //! `rustdoc`. +use std::collections::HashSet; use std::io::{self, Write}; use std::path::{Path, PathBuf}; use std::{env, fs, mem}; use crate::core::build_steps::compile; +use crate::core::build_steps::compile::{CargoMessage, stream_cargo}; use crate::core::build_steps::tool::{ self, RustcPrivateCompilers, SourceType, Tool, prepare_tool_cargo, }; @@ -20,9 +22,9 @@ use crate::core::builder::{ crate_description, }; use crate::core::compiler::Compiler; -use crate::core::config::{Config, TargetSelection}; +use crate::core::config::TargetSelection; use crate::core::session::{FileType, Mode}; -use crate::utils::helpers::{submodule_path_of, symlink_dir, t, up_to_date}; +use crate::utils::helpers::{exit_process, submodule_path_of, symlink_dir, t, up_to_date}; macro_rules! book { ($($name:ident, $path:expr, $book_name:expr, $lang:expr ;)+) => { @@ -840,7 +842,6 @@ fn doc_std( .arg("--no-deps") .arg("--target-dir") .arg(&*target_dir.to_string_lossy()) - .arg("-Zskip-rustdoc-fingerprint") .arg("-Zrustdoc-map") .rustdocflag("--extern-html-root-url") .rustdocflag("std_detect=https://docs.rs/std_detect/latest/") @@ -871,7 +872,159 @@ pub fn prepare_doc_compiler( build_compiler } +/// Run rustdoc to merge cross-crate info metadata (like the search index) from individual +/// executions of rustdoc into `out_dir`. +/// The `json_files` parameter should contain paths to JSON file artifacts generated by previous +/// executions of `cargo doc`. +fn merge_rustdoc_cci( + builder: &Builder<'_>, + build_compiler: Compiler, + json_files: &[PathBuf], + out_dir: &Path, +) { + let mut cmd = builder.rustdoc_cmd(build_compiler); + + cmd.arg("--enable-index-page").arg("-Zunstable-options").arg("-o").arg(out_dir); + + if !builder.config.docs_minification { + cmd.arg("--disable-minification"); + } + + for json_file in json_files { + cmd.arg("--read-doc-meta-dir").arg(json_file.parent().unwrap()); + } + + cmd.run(builder); +} + +/// Generate the combined compiler + tools docs for a given toolchain. +/// This contains both the compiler docs, docs of rustc_private tools (miri, clippy, etc.), cargo +/// and also some bootstrap related tools (bootstrap itself, compiletest, tidy, etc.). +/// +/// It gets hosted at https://doc.rust-lang.org/nightly/nightly-rustc/index.html. +/// +/// Compiler documentation is distributed separately, so we make sure +/// we do not merge it with the other documentation from std, test and +/// proc_macros. This is largely just a wrapper around `cargo doc`. +/// +/// Returns a path to a directory with the generated documentation. +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct CompilerWithTools { + build_compiler: Compiler, + target: TargetSelection, + stage: u32, +} + +impl CompilerWithTools { + /// Document `stage` compiler for the given `target`. + pub(crate) fn for_stage(builder: &Builder<'_>, stage: u32, target: TargetSelection) -> Self { + let build_compiler = prepare_doc_compiler(builder, target, stage); + Self { build_compiler, target, stage } + } +} + +impl CommandLineStep for CompilerWithTools { + type Output = PathBuf; + const IS_HOST: bool = true; + + fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { + run.alias("compiler-with-tools") + } + + fn is_default_step(builder: &Builder<'_>) -> bool { + builder.config.compiler_docs + } + + fn make_run(run: RunConfig<'_>) { + run.builder.ensure(CompilerWithTools::for_stage( + run.builder, + run.builder.top_stage, + run.target, + )); + } + + fn run(self, builder: &Builder<'_>) -> Self::Output { + let CompilerWithTools { target, build_compiler, stage } = self; + + // This is the intended out directory for combined compiler documentation. + let out = builder.compiler_doc_out(target); + let _ = fs::remove_dir_all(&out); + + let _guard = + builder.msg(Kind::Doc, "compiler-with-tools", Mode::Rustc, build_compiler, target); + + let combined_docs = vec![ + builder.ensure(Rustc::for_stage(builder, stage, target)), + builder.ensure(Rustdoc::new(builder, target)), + builder.ensure(Rustfmt::new(builder, target)), + builder.ensure(Clippy::new(builder, target)), + builder.ensure(Miri::new(builder, target)), + builder.ensure(Cargo::new(builder, target)), + builder.ensure(Tidy::new(builder, target)), + builder.ensure(Bootstrap::new(builder, target)), + builder.ensure(BuildHelper::new(builder, target)), + builder.ensure(Compiletest::new(builder, target)), + builder.ensure(RunMakeSupport::new(builder, target)), + ]; + + if !builder.config.dry_run() { + // Now copy all the individual docs into a single directory + let mut json_files = vec![]; + for docs in combined_docs { + json_files.extend(docs.artifacts.json_files); + + // Doc directories to link to the shared output directory + // We add the host doc dirs, which should already be symlinked in the target + // docs dir at this point (see `merge_host_and_target_docs`). + let dirs_to_copy: Vec<_> = docs + .artifacts + .target_dirs + .iter() + .chain(docs.artifacts.host_dirs.iter()) + .map(|d| d.file_name().unwrap().to_str().unwrap()) + .collect(); + for dir in dirs_to_copy { + // Link the docs dir + let docs_dir = docs.out_dir.join(dir); + assert!(docs_dir.exists(), "Docs directory {docs_dir:?} does not exist."); + let out_docs_dir = out.join(dir); + builder.create_dir(&out_docs_dir); + builder.cp_link_r(&docs_dir, &out_docs_dir); + + // And the src dir + let src_dir = docs.out_dir.join("src").join(dir); + assert!(src_dir.exists(), "Docs source directory {src_dir:?} does not exist."); + let out_src_dir = out.join("src").join(dir); + builder.create_dir(&out_src_dir); + builder.cp_link_r(&src_dir, &out_src_dir); + } + } + // And finally merge all the CCI metadata + merge_rustdoc_cci(builder, build_compiler, &json_files, &out); + } + + // Handle `--open`. + builder.open_in_browser(out.join("index.html")); + out + } + + fn metadata(&self) -> Option { + Some(StepMetadata::doc("CompilerWithTools", self.target).built_by(self.build_compiler)) + } +} + +/// Output of a Doc step. +#[derive(Clone)] +pub struct BuiltDocs { + /// Target doc directory with the generated documentation. + out_dir: PathBuf, + /// Doc artifacts gathered from Cargo during the doc build. + artifacts: DocArtifacts, +} + /// Document the compiler for the given `target` using rustdoc from `build_compiler`. +/// +/// Return the path to the generated rustc documentation directory. #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct Rustc { build_compiler: Compiler, @@ -901,7 +1054,7 @@ impl Rustc { } impl CommandLineStep for Rustc { - type Output = (); + type Output = BuiltDocs; const IS_HOST: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { @@ -922,13 +1075,9 @@ impl CommandLineStep for Rustc { /// Compiler documentation is distributed separately, so we make sure /// we do not merge it with the other documentation from std, test and /// proc_macros. This is largely just a wrapper around `cargo doc`. - fn run(self, builder: &Builder<'_>) { + fn run(self, builder: &Builder<'_>) -> Self::Output { let target = self.target; - // This is the intended out directory for compiler documentation. - let out = builder.compiler_doc_out(target); - t!(fs::create_dir_all(&out)); - // Build the standard library, so that proc-macros can use it. // (Normally, only the metadata would be necessary, but proc-macros are special since they run at compile-time.) let build_compiler = self.build_compiler; @@ -965,7 +1114,6 @@ impl CommandLineStep for Rustc { cargo.rustdocflag("--generate-macro-expansion"); compile::rustc_cargo(builder, &mut cargo, target, &build_compiler, &self.crates); - cargo.arg("-Zskip-rustdoc-fingerprint"); // Only include compiler crates, no dependencies of those, such as `libc`. // Do link to dependencies on `docs.rs` however using `rustdoc-map`. @@ -977,53 +1125,42 @@ impl CommandLineStep for Rustc { cargo.rustdocflag("--extern-html-root-url"); cargo.rustdocflag("ena=https://docs.rs/ena/latest/"); - let mut to_open = None; - - let out_dir = builder.stage_out(build_compiler, Mode::Rustc).join(target).join("doc"); + let cargo_target_dir = builder.stage_out(build_compiler, Mode::Rustc); + let target_doc_dir = cargo_target_dir.join(target).join("doc"); + let host_doc_dir = cargo_target_dir.join("doc"); for krate in &*self.crates { // Create all crate output directories first to make sure rustdoc uses // relative links. // FIXME: Cargo should probably do this itself. - let dir_name = krate.replace('-', "_"); - t!(fs::create_dir_all(out_dir.join(&*dir_name))); + let dir_name = normalize_doc_crate_name(krate); + t!(fs::create_dir_all(target_doc_dir.join(&*dir_name))); cargo.arg("-p").arg(krate); - if to_open.is_none() { - to_open = Some(dir_name); - } } - // This uses a shared directory so that librustdoc documentation gets - // correctly built and merged with the rustc documentation. - // - // This is needed because rustdoc is built in a different directory from - // rustc. rustdoc needs to be able to see everything, for example when - // merging the search index, or generating local (relative) links. - symlink_dir_force(&builder.config, &out, &out_dir); - // Cargo puts proc macros in `target/doc` even if you pass `--target` - // explicitly (https://github.com/rust-lang/cargo/issues/7677). - let proc_macro_out_dir = builder.stage_out(build_compiler, Mode::Rustc).join("doc"); - symlink_dir_force(&builder.config, &out, &proc_macro_out_dir); - - cargo.into_cmd().run(builder); + let artifacts = create_docs_and_gather_artifacts(builder, cargo); + artifacts.sanity_check_crates(builder, self.crates.iter()); if !builder.config.dry_run() { - // Sanity check on linked compiler crates - for krate in &*self.crates { - let dir_name = krate.replace('-', "_"); - // Making sure the directory exists and is not empty. - assert!(out.join(&*dir_name).read_dir().unwrap().next().is_some()); - } + merge_host_and_target_docs(builder, &artifacts, &host_doc_dir, &target_doc_dir); + merge_rustdoc_cci(builder, build_compiler, &artifacts.json_files, &target_doc_dir); } - if builder.paths.iter().any(|path| path.ends_with("compiler")) { - // For `x.py doc compiler --open`, open `rustc_middle` by default. - let index = out.join("rustc_middle").join("index.html"); - builder.open_in_browser(index); - } else if let Some(krate) = to_open { - // Let's open the first crate documentation page: - let index = out.join(krate).join("index.html"); + // We open rustc_middle as the default if invoked as `x.py doc --open RELEASES.md` + // with no particular explicit doc requested (e.g. library/core). + if builder.was_invoked_explicitly::(Kind::Doc) { + let index = if builder.paths.iter().any(|path| path.ends_with("compiler")) { + // For `x.py doc compiler --open`, open `rustc_middle` by default. + target_doc_dir.join("rustc_middle").join("index.html") + } else if let Some(krate) = self.crates.first() { + // Let's open the first crate documentation page: + target_doc_dir.join(normalize_doc_crate_name(krate)).join("index.html") + } else { + target_doc_dir.clone() + }; builder.open_in_browser(index); } + + BuiltDocs { out_dir: target_doc_dir, artifacts } } fn metadata(&self) -> Option { @@ -1031,6 +1168,145 @@ impl CommandLineStep for Rustc { } } +/// Stores generated documentation artifacts. +#[derive(Clone, Debug)] +struct DocArtifacts { + /// Directories with HTML docs for host (proc-macro) crates. + host_dirs: Vec, + /// Directories with HTML docs for target crates. + target_dirs: Vec, + /// JSON files used to create the final CCI index + json_files: Vec, +} + +impl DocArtifacts { + /// Ensure that all passed crates were documented. + fn sanity_check_crates(&self, builder: &Builder<'_>, crates: impl Iterator) + where + S: AsRef, + { + if builder.config.dry_run() { + return; + } + let crate_names: HashSet<&str> = self + .host_dirs + .iter() + .chain(self.target_dirs.iter()) + .filter_map(|d| d.file_name().and_then(|d| d.to_str())) + .collect(); + for krate in crates { + let krate = krate.as_ref(); + let krate = normalize_doc_crate_name(krate); + if !crate_names.contains(krate.as_str()) { + eprintln!("ERROR: crate {krate} was not documented!"); + exit_process(1); + } + } + } +} + +/// Run `cargo doc` and gather generated documentation artifacts. +fn create_docs_and_gather_artifacts(builder: &Builder<'_>, cargo: builder::Cargo) -> DocArtifacts { + let mut json_files = vec![]; + let mut host_dirs = vec![]; + let mut target_dirs = vec![]; + stream_cargo(builder, cargo, vec![], &mut |msg| { + let CargoMessage::CompilerArtifact { filenames, target } = msg else { + return; + }; + if !target.doc { + return; + } + // Note: An alternative way to check host docs would be to check whether the generated + // output is a child of the host doc directory (which we would have to pass to this + // function). + let is_host = target.crate_types.iter().any(|t| t == "proc-macro"); + for filename in filenames { + let path = Path::new(filename.as_ref()); + let Some(extension) = path.extension().and_then(|ext| ext.to_str()) else { + continue; + }; + let path = path.to_path_buf(); + if extension == "json" { + json_files.push(path.to_path_buf()); + } else if extension == "html" { + if is_host { + // doc//index.html -> doc/ + host_dirs.push(path.parent().unwrap().to_path_buf()); + } else { + target_dirs.push(path.parent().unwrap().to_path_buf()); + } + } + } + }); + DocArtifacts { host_dirs, target_dirs, json_files } +} + +/// Merge host and target documentation for a set of crates. +/// We pass `--target` when documenting, so Cargo will put the built documentation into two places: +/// - `target/doc` - contains documentation of host code, so proc macros +/// - `target//doc` - contains documentation of "normal" code +/// +/// See https://github.com/rust-lang/cargo/issues/7677. +/// +/// To produce a single unified documentation, we want to merge them together. +/// We do that by creating symlinks into the target doc dir that will point to the host doc +/// directories. +/// The target doc directory will then contain the combined docs. +fn merge_host_and_target_docs( + builder: &Builder<'_>, + docs: &DocArtifacts, + host_doc_dir: &Path, + target_doc_dir: &Path, +) { + // Sanity check that there is no host/target overlap + for dir in &docs.host_dirs { + let name = dir.file_name().and_then(|d| d.to_str()).unwrap(); + if let Some(target_dir) = docs.target_dirs.iter().find_map(|d| { + let dirname = d.file_name().and_then(|d| d.to_str())?; + if dirname == name { Some(d) } else { None } + }) { + eprintln!( + "ERROR: host docs directory `{name}` ({dir:?}) is also contained in target doc directory ({target_dir:?})" + ); + exit_process(1); + } + } + + let target_src_dir = target_doc_dir.join("src"); + let host_src_dir = host_doc_dir.join("src"); + + // Ideally, we would remove all previous symlinks here. + // However, some of the tools actually share the same build docs directory, so we shouldn't do + // that, otherwise they will invalidate one another. + + for host_docs_crate in &docs.host_dirs { + let dir_name = host_docs_crate.file_name().unwrap().to_str().unwrap(); + // Normalize crate name + let dir_name = normalize_doc_crate_name(dir_name); + + t!(symlink_dir(&builder.config, host_docs_crate, &target_doc_dir.join(&dir_name))); + + // Also symlink its source directory + let target_src_out = target_src_dir.join(&dir_name); + let host_src_out = host_src_dir.join(&dir_name); + t!(symlink_dir(&builder.config, &host_src_out, &target_src_out)); + } + + // Sanity check that all directories contain some documentation + for dir in docs.target_dirs.iter().chain(docs.host_dirs.iter()) { + // Making sure the directory exists and is not empty. + assert!(dir.exists(), "Doc directory {dir:?} does not exist"); + assert!(t!(dir.read_dir()).next().is_some(), "Doc directory {dir:?} is empty"); + } +} + +/// Normalizes crate name to get a name that is used to generate documentation on disk. +/// Turns `rustc-main` into `rustc_main`. +fn normalize_doc_crate_name(name: &str) -> String { + name.replace("-", "_") +} + macro_rules! tool_doc { ( $tool: ident, @@ -1048,46 +1324,53 @@ macro_rules! tool_doc { target: TargetSelection, } - impl CommandLineStep for $tool { - type Output = (); - const IS_HOST: bool = true; - - fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { - run.path($path) - } - - fn is_default_step(builder: &Builder<'_>) -> bool { - builder.config.compiler_docs - } - - fn make_run(run: RunConfig<'_>) { - let target = run.target; + impl $tool { + fn new(builder: &Builder<'_>, target: TargetSelection) -> $tool { let build_compiler = match $mode { Mode::ToolRustcPrivate => { // Rustdoc needs the rustc sysroot available to build. - let compilers = RustcPrivateCompilers::new(run.builder, run.builder.top_stage, target); + let compilers = RustcPrivateCompilers::new(builder, builder.top_stage, target); // Build rustc docs so that we generate relative links. - run.builder.ensure(Rustc::from_build_compiler(run.builder, compilers.build_compiler(), target)); + builder.ensure(Rustc::from_build_compiler(builder, compilers.build_compiler(), target)); compilers.build_compiler() } Mode::ToolTarget => { // when shipping multiple docs together in one folder, // they all need to use the same rustdoc version - prepare_doc_compiler(run.builder, run.builder.host_target, run.builder.top_stage) + prepare_doc_compiler(builder, builder.host_target, builder.top_stage) } _ => { panic!("Unexpected tool mode for documenting: {:?}", $mode); } }; + $tool { build_compiler, mode: $mode, target } + } + fn crates() -> &'static [&'static str] { + &$($crates)?[..] + } + } + + impl CommandLineStep for $tool { + type Output = BuiltDocs; + const IS_HOST: bool = true; - run.builder.ensure($tool { build_compiler, mode: $mode, target }); + fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { + run.path($path) + } + + fn is_default_step(builder: &Builder<'_>) -> bool { + builder.config.compiler_docs + } + + fn make_run(run: RunConfig<'_>) { + run.builder.ensure($tool::new(run.builder, run.target)); } /// Generates documentation for a tool. /// /// This is largely just a wrapper around `cargo doc`. - fn run(self, builder: &Builder<'_>) { + fn run(self, builder: &Builder<'_>) -> Self::Output { let mut source_type = SourceType::InTree; if let Some(submodule_path) = submodule_path_of(&builder, $path) { @@ -1097,10 +1380,6 @@ macro_rules! tool_doc { let $tool { build_compiler, mode, target } = self; - // This is the intended out directory for compiler documentation. - let out = builder.compiler_doc_out(target); - t!(fs::create_dir_all(&out)); - // Build cargo command. let mut cargo = prepare_tool_cargo( builder, @@ -1122,7 +1401,6 @@ macro_rules! tool_doc { cargo.allow_features(allow_features); } - cargo.arg("-Zskip-rustdoc-fingerprint"); // Only include compiler crates, no dependencies of those, such as `libc`. cargo.arg("--no-deps"); @@ -1130,9 +1408,9 @@ macro_rules! tool_doc { cargo.arg("--lib"); } - $(for krate in $crates { + for krate in $tool::crates() { cargo.arg("-p").arg(krate); - })? + } cargo.rustdocflag("--document-private-items"); // Since we always pass --document-private-items, there's no need to warn about linking to private items. @@ -1141,28 +1419,23 @@ macro_rules! tool_doc { cargo.rustdocflag("--show-type-layout"); cargo.rustdocflag("--generate-link-to-definition"); - let out_dir = builder.stage_out(build_compiler, mode).join(target).join("doc"); - $(for krate in $crates { - let dir_name = krate.replace("-", "_"); - t!(fs::create_dir_all(out_dir.join(&*dir_name))); - })? - - // Symlink compiler docs to the output directory of rustdoc documentation. - symlink_dir_force(&builder.config, &out, &out_dir); - let proc_macro_out_dir = builder.stage_out(build_compiler, mode).join("doc"); - symlink_dir_force(&builder.config, &out, &proc_macro_out_dir); + let cargo_target_dir = builder.stage_out(build_compiler, mode); + let target_doc_dir = cargo_target_dir.join(target).join("doc"); + let host_doc_dir = cargo_target_dir.join("doc"); + for krate in $tool::crates() { + let dir_name = normalize_doc_crate_name(krate); + t!(fs::create_dir_all(target_doc_dir.join(&*dir_name))); + } let _guard = builder.msg(Kind::Doc, stringify!($tool).to_lowercase(), None, build_compiler, target); - cargo.into_cmd().run(builder); + let artifacts = create_docs_and_gather_artifacts(builder, cargo); + artifacts.sanity_check_crates(builder, $tool::crates().iter()); if !builder.config.dry_run() { - // Sanity check on linked doc directories - $(for krate in $crates { - let dir_name = krate.replace("-", "_"); - // Making sure the directory exists and is not empty. - assert!(out.join(&*dir_name).read_dir().unwrap().next().is_some()); - })? + merge_host_and_target_docs(builder, &artifacts, &host_doc_dir, &target_doc_dir); + merge_rustdoc_cci(builder, build_compiler, &artifacts.json_files, &target_doc_dir); } + BuiltDocs { out_dir: target_doc_dir, artifacts } } fn metadata(&self) -> Option { @@ -1344,26 +1617,6 @@ impl CommandLineStep for UnstableBookGen { } } -fn symlink_dir_force(config: &Config, original: &Path, link: &Path) { - if config.dry_run() { - return; - } - if let Ok(m) = fs::symlink_metadata(link) { - if m.file_type().is_dir() { - t!(fs::remove_dir_all(link)); - } else { - // handle directory junctions on windows by falling back to - // `remove_dir`. - t!(fs::remove_file(link).or_else(|_| fs::remove_dir(link))); - } - } - - t!( - symlink_dir(config, original, link), - format!("failed to create link from {} -> {}", link.display(), original.display()) - ); -} - /// Builds the Rust compiler book. #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct RustcBook { diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs index 754f4a547bd74..98cfd58198a6c 100644 --- a/src/bootstrap/src/core/builder/cargo.rs +++ b/src/bootstrap/src/core/builder/cargo.rs @@ -723,6 +723,12 @@ impl Builder<'_> { } if cmd_kind == Kind::Doc { + // Will be stabilized soon -> let's dogfood it. + // No effect on doc output but massive doc-generation time improvements. + cargo.arg("-Zrustdoc-mergeable-info"); + + // FIXME: remove this directory clearing here, and do it explicitly in individua doc + // steps, to reduce dependency on implicit doc output paths. let my_out = match mode { // This is the intended out directory for compiler documentation. Mode::Rustc | Mode::ToolRustcPrivate | Mode::ToolBootstrap | Mode::ToolTarget => { diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs index c221a976b2698..f8b4ebb5841c7 100644 --- a/src/bootstrap/src/core/builder/mod.rs +++ b/src/bootstrap/src/core/builder/mod.rs @@ -965,6 +965,7 @@ impl<'a> Builder<'a> { doc::CargoBook, doc::Clippy, doc::ClippyBook, + doc::CompilerWithTools, doc::Miri, doc::EmbeddedBook, doc::EditionGuide, diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index dd1beb65e7fe0..893fc4f7ee017 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -989,58 +989,6 @@ mod snapshot { ); } - #[test] - fn dist_compiler_docs() { - let ctx = TestCtx::new(); - insta::assert_snapshot!( - ctx.config("dist") - .path("rustc-docs") - .args(&["--set", "build.compiler-docs=true"]) - .render_steps(), @r" - [build] llvm - [build] rustc 0 -> rustc 1 - [build] rustc 1 -> std 1 - [build] rustc 0 -> UnstableBookGen 1 - [build] rustc 0 -> Rustbook 1 - [doc] unstable-book (book) - [doc] book (book) - [doc] book/first-edition (book) - [doc] book/second-edition (book) - [doc] book/2018-edition (book) - [build] rustdoc 1 - [doc] rustc 1 -> standalone 2 - [doc] rustc 1 -> std 1 crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind] - [doc] rustc 1 -> rustc 2 - [build] rustc 1 -> rustc 2 - [doc] rustc 1 -> Rustdoc 2 - [doc] rustc 1 -> Rustfmt 2 - [build] rustc 1 -> error-index 2 - [doc] rustc 1 -> error-index 2 - [doc] nomicon (book) - [doc] rustc 1 -> reference (book) 2 - [doc] rustdoc (book) - [doc] rust-by-example (book) - [build] rustc 0 -> LintDocs 1 - [doc] rustc (book) - [doc] rustc 1 -> Cargo 2 - [doc] cargo (book) - [doc] rustc 1 -> Clippy 2 - [doc] clippy (book) - [doc] rustc 1 -> Miri 2 - [doc] embedded-book (book) - [doc] edition-guide (book) - [doc] style-guide (book) - [doc] rustc 1 -> Tidy 2 - [doc] rustc 1 -> Bootstrap 2 - [doc] rustc 1 -> releases 2 - [doc] rustc 1 -> RunMakeSupport 2 - [doc] rustc 1 -> BuildHelper 2 - [doc] rustc 1 -> Compiletest 2 - [build] rustc 0 -> RustInstaller 1 - " - ); - } - #[test] fn dist_extended() { let ctx = TestCtx::new(); @@ -1624,35 +1572,24 @@ mod snapshot { ctx .config("dist") .path("rustc-docs") - .render_steps(), @r" + .render_steps(), @" [build] llvm [build] rustc 0 -> rustc 1 [build] rustc 1 -> std 1 - [build] rustc 0 -> UnstableBookGen 1 - [build] rustc 0 -> Rustbook 1 - [doc] unstable-book (book) - [doc] book (book) - [doc] book/first-edition (book) - [doc] book/second-edition (book) - [doc] book/2018-edition (book) [build] rustdoc 1 - [doc] rustc 1 -> standalone 2 - [doc] rustc 1 -> std 1 crates=[alloc,compiler_builtins,core,panic_abort,panic_unwind,proc_macro,rustc-std-workspace-core,std,std_detect,sysroot,test,unwind] + [doc] rustc 1 -> rustc 2 [build] rustc 1 -> rustc 2 - [build] rustc 1 -> error-index 2 - [doc] rustc 1 -> error-index 2 - [doc] nomicon (book) - [doc] rustc 1 -> reference (book) 2 - [doc] rustdoc (book) - [doc] rust-by-example (book) - [build] rustc 0 -> LintDocs 1 - [doc] rustc (book) - [doc] cargo (book) - [doc] clippy (book) - [doc] embedded-book (book) - [doc] edition-guide (book) - [doc] style-guide (book) - [doc] rustc 1 -> releases 2 + [doc] rustc 1 -> Rustdoc 2 + [doc] rustc 1 -> Rustfmt 2 + [doc] rustc 1 -> Clippy 2 + [doc] rustc 1 -> Miri 2 + [doc] rustc 1 -> Cargo 2 + [doc] rustc 1 -> Tidy 2 + [doc] rustc 1 -> Bootstrap 2 + [doc] rustc 1 -> BuildHelper 2 + [doc] rustc 1 -> Compiletest 2 + [doc] rustc 1 -> RunMakeSupport 2 + [doc] rustc 1 -> CompilerWithTools 2 [build] rustc 0 -> RustInstaller 1 "); } @@ -2442,6 +2379,31 @@ mod snapshot { "); } + #[test] + fn doc_compiler_with_tools() { + let ctx = TestCtx::new(); + insta::assert_snapshot!( + ctx.config("doc") + .arg("compiler-with-tools") + .render_steps(), @" + [build] rustdoc 0 + [doc] rustc 0 -> rustc 1 + [build] llvm + [build] rustc 0 -> rustc 1 + [doc] rustc 0 -> Rustdoc 1 + [doc] rustc 0 -> Rustfmt 1 + [doc] rustc 0 -> Clippy 1 + [doc] rustc 0 -> Miri 1 + [doc] rustc 0 -> Cargo 1 + [doc] rustc 0 -> Tidy 1 + [doc] rustc 0 -> Bootstrap 1 + [doc] rustc 0 -> BuildHelper 1 + [doc] rustc 0 -> Compiletest 1 + [doc] rustc 0 -> RunMakeSupport 1 + [doc] rustc 0 -> CompilerWithTools 1 + "); + } + #[test] fn doc_cargo_stage_1() { let ctx = TestCtx::new(); diff --git a/src/bootstrap/src/core/session.rs b/src/bootstrap/src/core/session.rs index 4ad04df753d00..1808f1b8855bc 100644 --- a/src/bootstrap/src/core/session.rs +++ b/src/bootstrap/src/core/session.rs @@ -810,7 +810,7 @@ impl Session { self.out.join(target).join("json-doc") } - /// Output directory for all documentation for a target + /// Output directory for combined compiler + tools docs. pub(crate) fn compiler_doc_out(&self, target: TargetSelection) -> PathBuf { self.out.join(target).join("compiler-doc") } diff --git a/tests/incremental/lto.rs b/tests/incremental/lto.rs index ddeddf95f8aee..7e3c5f2f42bf6 100644 --- a/tests/incremental/lto.rs +++ b/tests/incremental/lto.rs @@ -1,11 +1,20 @@ //@ no-prefer-dynamic //@ revisions:rpass1 rpass2 -//@ compile-flags: -C lto +//@ compile-flags: -Z query-dep-graph -C lto=fat //@ ignore-backends: gcc +#![feature(rustc_attrs)] +#![rustc_partition_codegened(module = "lto", cfg = "rpass1")] +#![rustc_partition_codegened(module = "lto-x", cfg = "rpass1")] +#![rustc_partition_codegened(module = "lto-y", cfg = "rpass1")] +#![rustc_partition_reused(module = "lto", cfg = "rpass2")] +#![rustc_partition_codegened(module = "lto-x", cfg = "rpass2")] +#![rustc_partition_reused(module = "lto-y", cfg = "rpass2")] + mod x { pub struct X { - x: u32, y: u32, + x: u32, + y: u32, } #[cfg(rpass1)] diff --git a/tests/rustdoc-json/impls/blanket-ambig-on-nonrigid-assoc-next-solver.rs b/tests/rustdoc-json/impls/blanket-ambig-on-nonrigid-assoc-next-solver.rs new file mode 100644 index 0000000000000..8f3af90a2598e --- /dev/null +++ b/tests/rustdoc-json/impls/blanket-ambig-on-nonrigid-assoc-next-solver.rs @@ -0,0 +1,23 @@ +//@ compile-flags: -Znext-solver + +// Regression test for + +pub trait Service { + type Future; +} + +pub trait ZebraService: Service {} + +impl ZebraService for MaybeVerify where + MaybeVerify: Service +{ +} + +pub struct Verifier; + +impl Service<()> for Verifier { + type Future = &'static (); +} + +//@ set blanket = "$.index[?(@.inner.impl.blanket_impl.generic=='MaybeVerify')].id" +//@ has "$.index[?(@.name=='Verifier')].inner.struct.impls[*]" $blanket diff --git a/tests/ui/feature-gates/feature-gate-autodiff-use.rs b/tests/ui/feature-gates/feature-gate-autodiff-use.rs index d97c06de7de5b..85608be4ac04a 100644 --- a/tests/ui/feature-gates/feature-gate-autodiff-use.rs +++ b/tests/ui/feature-gates/feature-gate-autodiff-use.rs @@ -1,22 +1,16 @@ -//@ revisions: nightly stable -//@[nightly] only-nightly -//@[stable] only-stable - // This checks that without enabling the autodiff feature, we can't import std::autodiff::autodiff; #![crate_type = "lib"] use std::autodiff::autodiff_reverse; -//[nightly]~^ ERROR use of unstable library feature `autodiff` -//[stable]~^^ ERROR use of unstable library feature `autodiff` -//[stable]~| NOTE see issue #124509 for more information -//[stable]~| HELP add `#![feature(autodiff)]` to the crate attributes to enable -//[stable]~| NOTE this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date +//~^ ERROR use of unstable library feature `autodiff` +//| NOTE see issue #124509 for more information +//| HELP add `#![feature(autodiff)]` to the crate attributes to enable +//| NOTE this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date #[autodiff_reverse(dfoo)] -//[nightly]~^ ERROR use of unstable library feature `autodiff` [E0658] -//[stable]~^^ ERROR use of unstable library feature `autodiff` [E0658] -//[stable]~| NOTE see issue #124509 for more information -//[stable]~| HELP add `#![feature(autodiff)]` to the crate attributes to enable -//[stable]~| NOTE this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date +//~^ ERROR use of unstable library feature `autodiff` [E0658] +//| NOTE see issue #124509 for more information +//| HELP add `#![feature(autodiff)]` to the crate attributes to enable +//| NOTE this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date fn foo() {} diff --git a/tests/ui/feature-gates/feature-gate-autodiff-use.stable.stderr b/tests/ui/feature-gates/feature-gate-autodiff-use.stable.stderr deleted file mode 100644 index ca724632063d4..0000000000000 --- a/tests/ui/feature-gates/feature-gate-autodiff-use.stable.stderr +++ /dev/null @@ -1,23 +0,0 @@ -error[E0658]: use of unstable library feature `autodiff` - --> $DIR/feature-gate-autodiff-use.rs:16:3 - | -LL | #[autodiff_reverse(dfoo)] - | ^^^^^^^^^^^^^^^^ - | - = note: see issue #124509 for more information - = help: add `#![feature(autodiff)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date - -error[E0658]: use of unstable library feature `autodiff` - --> $DIR/feature-gate-autodiff-use.rs:9:5 - | -LL | use std::autodiff::autodiff_reverse; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: see issue #124509 for more information - = help: add `#![feature(autodiff)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date - -error: aborting due to 2 previous errors - -For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/feature-gates/feature-gate-autodiff-use.nightly.stderr b/tests/ui/feature-gates/feature-gate-autodiff-use.stderr similarity index 91% rename from tests/ui/feature-gates/feature-gate-autodiff-use.nightly.stderr rename to tests/ui/feature-gates/feature-gate-autodiff-use.stderr index ca724632063d4..94b9a065e491e 100644 --- a/tests/ui/feature-gates/feature-gate-autodiff-use.nightly.stderr +++ b/tests/ui/feature-gates/feature-gate-autodiff-use.stderr @@ -1,5 +1,5 @@ error[E0658]: use of unstable library feature `autodiff` - --> $DIR/feature-gate-autodiff-use.rs:16:3 + --> $DIR/feature-gate-autodiff-use.rs:11:3 | LL | #[autodiff_reverse(dfoo)] | ^^^^^^^^^^^^^^^^ @@ -9,7 +9,7 @@ LL | #[autodiff_reverse(dfoo)] = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: use of unstable library feature `autodiff` - --> $DIR/feature-gate-autodiff-use.rs:9:5 + --> $DIR/feature-gate-autodiff-use.rs:5:5 | LL | use std::autodiff::autodiff_reverse; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/intrinsics/float-mul-add-relaxed.rs b/tests/ui/intrinsics/float-mul-add-relaxed.rs new file mode 100644 index 0000000000000..5ab9d52a88d48 --- /dev/null +++ b/tests/ui/intrinsics/float-mul-add-relaxed.rs @@ -0,0 +1,87 @@ +//@ run-pass +//@ compile-flags: -O + +// Check that `mul_add_relaxed` returns either the fused result (one rounding) +// or the unfused result (two roundings), including with optimizations enabled +// where the operation may be const-folded or lowered to a fused instruction. + +#![feature(float_mul_add_relaxed)] +#![feature(f16)] +#![feature(f128)] +#![feature(cfg_target_has_reliable_f16_f128)] +// `f16`/`f128` go unused on targets without reliable f16/f128 math, where the +// gated test functions below are compiled out. +#![allow(unused_features)] +// `target_has_reliable_*` are not "known" configs since they are unstable. +#![expect(unexpected_cfgs)] + +use std::assert_matches; +use std::hint::black_box; + +// On x86 without SSE2 (e.g. the i586 target), floating-point math is evaluated +// with x87 excess precision, so an inexact `mul_add_relaxed` may round to a +// value that is neither the `fN`-precise fused nor unfused result. Skip the +// exact-value checks there. +const EXCESS_PRECISION: bool = cfg!(all(target_arch = "x86", not(target_feature = "sse2"))); + +fn main() { + test_f32(); + test_f64(); + #[cfg(target_has_reliable_f16_math)] + test_f16(); + #[cfg(target_has_reliable_f128_math)] + test_f128(); +} + +fn test_f32() { + // Exactly representable results are the same whether or not the + // operation is fused. + assert_eq!(black_box(2.0_f32).mul_add_relaxed(3.0, 4.0), 10.0); + assert_eq!(black_box(1.0_f32).mul_add_relaxed(1.0, 1.0), 2.0); + + // `0.1 * 0.1` is inexact, so the fused (one rounding) and unfused (two + // roundings) results differ; either is allowed. + if !EXCESS_PRECISION { + let r = black_box(0.1_f32).mul_add_relaxed(0.1, -0.01); + assert_matches!(r, 5.2154064e-10 | 9.313226e-10); + } + + // Edge cases behave like `a * b + c` regardless of fusion. + assert!(black_box(f32::NAN).mul_add_relaxed(1.0, 1.0).is_nan()); + assert_eq!(black_box(f32::INFINITY).mul_add_relaxed(2.0, 1.0), f32::INFINITY); + assert!(black_box(0.0_f32).mul_add_relaxed(f32::INFINITY, 1.0).is_nan()); +} + +fn test_f64() { + assert_eq!(black_box(2.0_f64).mul_add_relaxed(3.0, 4.0), 10.0); + assert_eq!(black_box(1.0_f64).mul_add_relaxed(1.0, 1.0), 2.0); + + if !EXCESS_PRECISION { + let r = black_box(0.1_f64).mul_add_relaxed(0.1, -0.01); + assert_matches!(r, 9.020562075079397e-19 | 1.734723475976807e-18); + } + + assert!(black_box(f64::NAN).mul_add_relaxed(1.0, 1.0).is_nan()); + assert_eq!(black_box(f64::INFINITY).mul_add_relaxed(2.0, 1.0), f64::INFINITY); + assert!(black_box(0.0_f64).mul_add_relaxed(f64::INFINITY, 1.0).is_nan()); +} + +#[cfg(target_has_reliable_f16_math)] +fn test_f16() { + assert_eq!(black_box(2.0_f16).mul_add_relaxed(3.0, 4.0), 10.0); + assert_eq!(black_box(1.0_f16).mul_add_relaxed(1.0, 1.0), 2.0); + + assert!(black_box(f16::NAN).mul_add_relaxed(1.0, 1.0).is_nan()); + assert_eq!(black_box(f16::INFINITY).mul_add_relaxed(2.0, 1.0), f16::INFINITY); + assert!(black_box(0.0_f16).mul_add_relaxed(f16::INFINITY, 1.0).is_nan()); +} + +#[cfg(target_has_reliable_f128_math)] +fn test_f128() { + assert_eq!(black_box(2.0_f128).mul_add_relaxed(3.0, 4.0), 10.0); + assert_eq!(black_box(1.0_f128).mul_add_relaxed(1.0, 1.0), 2.0); + + assert!(black_box(f128::NAN).mul_add_relaxed(1.0, 1.0).is_nan()); + assert_eq!(black_box(f128::INFINITY).mul_add_relaxed(2.0, 1.0), f128::INFINITY); + assert!(black_box(0.0_f128).mul_add_relaxed(f128::INFINITY, 1.0).is_nan()); +}