diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 0a9af492b1f45..4de9ac16ec96a 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -16,6 +16,7 @@ use regex::Regex; use rustc_arena::TypedArena; use rustc_attr_parsing::eval_config_entry; use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; +use rustc_data_structures::jobserver; use rustc_data_structures::memmap::Mmap; use rustc_data_structures::temp_dir::MaybeTempDir; use rustc_errors::DiagCtxtHandle; @@ -36,8 +37,8 @@ use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerFile; use rustc_middle::middle::dependency_format::Linkage; use rustc_middle::middle::exported_symbols::SymbolExportKind; use rustc_session::config::{ - self, CFGuard, CrateType, DebugInfo, InstrumentMcount, LinkerFeaturesCli, OutFileName, - OutputFilenames, OutputType, PrintKind, SplitDwarfKind, Strip, + self, CFGuard, CrateType, DebugInfo, InstrumentMcount, LinkerFeaturesCli, LinkerJobs, + OutFileName, OutputFilenames, OutputType, PrintKind, SplitDwarfKind, Strip, }; use rustc_session::lint::builtin::LINKER_MESSAGES; use rustc_session::output::{check_file_is_writeable, invalid_output_for_target, out_filename}; @@ -1085,7 +1086,7 @@ fn link_natively( should_archive.then(|| tmpdir.join(out_filename.file_name().unwrap()).with_extension("so")); let temp_filename = archive_member.as_deref().unwrap_or(out_filename); - let mut cmd = linker_with_args( + let (mut cmd, jobserver_tokens) = linker_with_args( &linker_path, flavor, sess, @@ -1242,6 +1243,9 @@ fn link_natively( break; } + // Finished running linker, release the tokens. + drop(jobserver_tokens); + match prog { Ok(prog) => { if !prog.status.success() { @@ -2736,7 +2740,7 @@ fn linker_with_args( metadata: &EncodedMetadata, self_contained_components: LinkSelfContainedComponents, codegen_backend: &'static str, -) -> Command { +) -> (Command, Vec) { let self_contained_crt_objects = self_contained_components.is_crt_objects_enabled(); let cmd = &mut *super::linker::get_linker( sess, @@ -3012,7 +3016,38 @@ fn linker_with_args( // to it and remove the option. Currently the last holdout is wasm32-unknown-emscripten. add_post_link_args(cmd, sess, flavor); - cmd.take_cmd() + // Only LLD supports controlling parallelism at the moment. + let mut tokens = Vec::new(); + if let LinkerJobs::Explicit(limit) = sess.opts.jobs.linker + && flavor.uses_lld() + { + // Try obtaining as many jobserver tokens as possible (within the limit) to run parallel + // linking. One token is available implicitly since we are running on the main thread. + let client = jobserver::client(); + + let mut unsupported = false; + for _ in 0..limit.get() - 1 { + match client.try_acquire() { + Ok(Some(token)) => tokens.push(token), + Ok(None) => {} + Err(e) if e.kind() == io::ErrorKind::Unsupported => { + assert!(tokens.is_empty()); + unsupported = true; + break; + } + Err(e) => bug!("IO error when acquiring jobserver token: {e}"), + } + } + + let prefix = if sess.target.is_like_windows { "/threads:" } else { "--threads=" }; + // Error on the side of oversubscription if non-blocking token acquiring is unsupported. + // Linking is typically the last step in a multi-crate project build, + // so the resources should usually be free. + let threads = if unsupported { limit.get() } else { 1 + tokens.len() }; + cmd.link_arg(format!("{prefix}{threads}")); + } + + (cmd.take_cmd(), tokens) } fn add_order_independent_options( diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index 1db2321f7b249..07d8a80c2c416 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -354,7 +354,7 @@ pub struct CodegenContext { pub incr_comp_session_dir: Option, /// `true` if the codegen should be run in parallel. /// - /// Depends on [`WriteBackendMethods::supports_parallel()`] and `-Zno_parallel_backend`. + /// Depends on [`WriteBackendMethods::supports_parallel()`] and `--jobs-backend`. pub parallel: bool, } @@ -1251,7 +1251,11 @@ fn start_executing_work( // Note that using `jobserver::Proxy` is not necessary here, the code below always acquires // tokens before releasing them, so we can never accidentally release the last token // permanently held by rustc process. - let parallel = !sess.opts.unstable_opts.no_parallel_backend && backend.supports_parallel(); + // FIXME: the backend parallelism is currently limited solely by the jobserver, + // so if `--jobs-backend` is smaller than `--jobs(-frontend)`, or than the number of tokens + // that the external jobserver can give, then it won't be respected. + // Below we'll need to add some additional work limiting for `--jobs-backend` to be respected. + let parallel = sess.opts.jobs.backend.is_some() && backend.supports_parallel(); let jobserver_helper = parallel.then(|| { let coordinator_send2 = coordinator_send.clone(); jobserver::client() diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index d3af6eba33374..a1239cab05598 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -807,14 +807,14 @@ pub fn codegen_crate< // This likely is a temporary measure. Once we don't have to support the // non-parallel compiler anymore, we can compile CGUs end-to-end in // parallel and get rid of the complicated scheduling logic. - let mut pre_compiled_cgus = if let Some(threads) = tcx.sess.threads() { + let mut pre_compiled_cgus = if let Some(threads) = tcx.sess.opts.jobs.frontend { tcx.sess.time("compile_first_CGU_batch", || { // Try to find one CGU to compile per thread. let cgus: Vec<_> = cgu_reuse .iter() .enumerate() .filter(|&(_, reuse)| reuse == &CguReuse::No) - .take(threads) + .take(threads.get()) .collect(); // Compile the found CGUs in parallel. diff --git a/compiler/rustc_data_structures/src/jobserver.rs b/compiler/rustc_data_structures/src/jobserver.rs index 6f275d106d640..81c3134e8da39 100644 --- a/compiler/rustc_data_structures/src/jobserver.rs +++ b/compiler/rustc_data_structures/src/jobserver.rs @@ -1,12 +1,10 @@ -use std::sync::{Arc, LazyLock, OnceLock}; +use std::sync::{Arc, OnceLock}; pub use jobserver_crate::Acquired; use jobserver_crate::{Client, FromEnv, FromEnvErrorKind, HelperThread}; use parking_lot::{Condvar, Mutex}; -// We stick the jobserver client into a global and initialize it once, because there could be -// multiple compiler instances in this process, and the jobserver is per-process. -static GLOBAL_CLIENT: LazyLock> = LazyLock::new(|| { +fn create_client(limit: usize) -> Result { // Safety: the checked client construction ensures that the jobserver file descriptors // (if any) are open and valid. We also try to initialize the jobserver as early as possible // to avoid unrelated file descriptors with matching values becoming open and valid between @@ -25,7 +23,7 @@ static GLOBAL_CLIENT: LazyLock> = LazyLock::new(|| { | FromEnvErrorKind::NegativeFd | FromEnvErrorKind::Unsupported ) { - return Ok(default_client()); + return Ok(default_client(limit)); } // Environment specifies jobserver, but it looks incorrect. @@ -35,14 +33,11 @@ static GLOBAL_CLIENT: LazyLock> = LazyLock::new(|| { "failed to connect to jobserver from environment variable `{name}={:?}`: {error}", value )) -}); +} // Creates a new jobserver if there's no inherited one. -fn default_client() -> Client { - // Pick a "reasonable maximum" capping out at 32 - // so we don't take everything down by hogging the process run queue. - // The fixed number is used to have deterministic compilation across machines. - let client = Client::new(32).expect("failed to create jobserver"); +fn default_client(limit: usize) -> Client { + let client = Client::new(limit).expect("failed to create jobserver"); // Acquire the single token that is always held by the rustc process. // This is an equivalent of the single token held by a higher level build tool while running @@ -53,21 +48,22 @@ fn default_client() -> Client { client } +// We stick the jobserver client into a global and initialize it once, because there could be +// multiple compiler instances in this process, and the jobserver is per-process. static GLOBAL_CLIENT_CHECKED: OnceLock = OnceLock::new(); /// Initializes a jobserver client for the current rustc process. /// If inheriting jobserver from the environment fails for some reason, an new jobserver owned by /// the current rustc process will be created. If the inheritance failure reason is non-benign, /// the passed callback will be used to report the error. -pub fn initialize_checked(report: impl FnOnce(&'static str)) { - let client_checked = match &*GLOBAL_CLIENT { - Ok(client) => client.clone(), +pub fn initialize_checked(limit: usize, report: impl FnOnce(String)) { + GLOBAL_CLIENT_CHECKED.get_or_init(|| match create_client(limit) { + Ok(client) => client, Err(e) => { report(e); - default_client() + default_client(limit) } - }; - GLOBAL_CLIENT_CHECKED.set(client_checked).ok(); + }); } /// Returns the jobserver client previously initialized by `initialize_checked`. diff --git a/compiler/rustc_interface/src/interface.rs b/compiler/rustc_interface/src/interface.rs index b0539b973c201..8701221ae4707 100644 --- a/compiler/rustc_interface/src/interface.rs +++ b/compiler/rustc_interface/src/interface.rs @@ -15,7 +15,7 @@ use rustc_parse::lexer::StripTokens; use rustc_parse::new_parser_from_source_str; use rustc_parse::parser::Recovery; use rustc_query_impl::print_query_stack; -use rustc_session::config::{self, Cfg, CheckCfg, ExpectedValues, Input, OutFileName}; +use rustc_session::config::{self, BackendJobs, Cfg, CheckCfg, ExpectedValues, Input, OutFileName}; use rustc_session::parse::ParseSess; use rustc_session::{CompilerIO, EarlyDiagCtxt, Session, lint}; use rustc_span::source_map::{FileLoader, RealFileLoader, SourceMapInputs}; @@ -371,18 +371,20 @@ pub fn run_compiler(config: Config, f: impl FnOnce(&Compiler) -> R + Se trace!("run_compiler"); // Set parallel mode before thread pool creation, which will create `Lock`s. - rustc_data_structures::sync::set_dyn_thread_safe_mode( - config.opts.unstable_opts.threads.is_some(), - ); + rustc_data_structures::sync::set_dyn_thread_safe_mode(config.opts.jobs.frontend.is_some()); // Initialize jobserver as early as possible. let early_dcx = EarlyDiagCtxt::new(config.opts.error_format); - jobserver::initialize_checked(|err| { - early_dcx - .early_struct_warn(err) - .with_note("the build environment is likely misconfigured") - .emit() - }); + if let Some(limit) = + config.opts.jobs.frontend.max(config.opts.jobs.backend.map(BackendJobs::value)) + { + jobserver::initialize_checked(limit.get(), |err| { + early_dcx + .early_struct_warn(err) + .with_note("the build environment is likely misconfigured") + .emit() + }); + } crate::callbacks::setup_callbacks(); @@ -400,7 +402,7 @@ pub fn run_compiler(config: Config, f: impl FnOnce(&Compiler) -> R + Se util::run_in_thread_pool_with_globals( &early_dcx, config.opts.edition, - config.opts.unstable_opts.threads.unwrap_or(1), + config.opts.jobs, &config.extra_symbols, SourceMapInputs { file_loader, path_mapping, hash_kind, checksum_hash_kind }, |current_gcx| { diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index 22b643e74e582..2e262dd9291b4 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -756,7 +756,7 @@ fn test_unstable_options_tracking_hash() { untracked!(span_debug, true); untracked!(span_free_formats, true); untracked!(temps_dir, Some(String::from("abc"))); - untracked!(threads, Some(99)); + untracked!(threads, Some(String::from("99"))); untracked!(time_llvm_passes, true); untracked!(time_passes, true); untracked!(time_passes_format, TimePassesFormat::Json); diff --git a/compiler/rustc_interface/src/util.rs b/compiler/rustc_interface/src/util.rs index 39c5ee8193256..019c7ccfe979a 100644 --- a/compiler/rustc_interface/src/util.rs +++ b/compiler/rustc_interface/src/util.rs @@ -1,5 +1,6 @@ use std::any::Any; use std::env::consts::{DLL_PREFIX, DLL_SUFFIX}; +use std::num::NonZero; use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, OnceLock}; @@ -21,7 +22,7 @@ use rustc_middle::dep_graph::WorkProductMap; use rustc_middle::ty::{CurrentGcx, TyCtxt}; use rustc_query_impl::{CollectActiveJobsKind, collect_active_query_jobs}; use rustc_session::config::{ - Cfg, CrateType, OutFileName, OutputFilenames, OutputTypes, Sysroot, host_tuple, + Cfg, CrateType, Jobs, OutFileName, OutputFilenames, OutputTypes, Sysroot, host_tuple, }; use rustc_session::{EarlyDiagCtxt, Session, filesearch}; use rustc_span::edition::Edition; @@ -175,7 +176,7 @@ fn run_in_thread_with_globals R + Send, R: Send>( pub(crate) fn run_in_thread_pool_with_globals R + Send, R: Send>( thread_builder_diag: &EarlyDiagCtxt, edition: Edition, - threads: usize, + jobs: Jobs, extra_symbols: &[&'static str], sm_inputs: SourceMapInputs, f: F, @@ -188,9 +189,11 @@ pub(crate) fn run_in_thread_pool_with_globals R + Send, let thread_stack_size = init_stack_size(thread_builder_diag); - let registry = sync::Registry::new(std::num::NonZero::new(threads).unwrap()); + let jobs_frontend = jobs.frontend.or(NonZero::new(1)).unwrap(); + let registry = sync::Registry::new(jobs_frontend); let Some(proof) = sync::check_dyn_thread_safe() else { + assert_eq!(jobs_frontend.get(), 1); return run_in_thread_with_globals( thread_stack_size, edition, @@ -215,7 +218,7 @@ pub(crate) fn run_in_thread_pool_with_globals R + Send, .thread_name(|_| "rustc".to_string()) .acquire_thread_handler(move || proxy.acquire_thread()) .release_thread_handler(move || proxy_.release_thread()) - .num_threads(threads) + .num_threads(jobs_frontend.get()) .deadlock_handler(move || { // On deadlock, creates a new thread and forwards information in thread // locals to it. The new thread runs the deadlock handler. @@ -295,7 +298,7 @@ internal compiler error: query cycle handler thread panicked, aborting process"; ) .unwrap_or_else(|err| { let mut diag = thread_builder_diag.early_struct_fatal(format!( - "failed to spawn compiler thread pool: could not create {threads} threads ({err})", + "failed to spawn compiler thread pool: could not create {jobs_frontend} threads ({err})", )); diag.help( "try lowering `-Z threads` or checking the operating system's resource limits", diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 7209e3d8ec338..d4f1610dda4dc 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -2487,7 +2487,7 @@ pub fn encode_metadata(tcx: TyCtxt<'_>, path: &Path, ref_path: Option<&Path>) { return; }; - if tcx.sess.threads().is_some() { + if tcx.sess.opts.jobs.frontend.is_some() { // Prefetch some queries used by metadata encoding. // This is not necessary for correctness, but is only done for performance reasons. // It can be removed if it turns out to cause trouble or be detrimental to performance. diff --git a/compiler/rustc_middle/src/dep_graph/graph.rs b/compiler/rustc_middle/src/dep_graph/graph.rs index 66ccde118a6f7..853288385b16c 100644 --- a/compiler/rustc_middle/src/dep_graph/graph.rs +++ b/compiler/rustc_middle/src/dep_graph/graph.rs @@ -676,7 +676,7 @@ impl DepGraphData { let ok = match color { DepNodeColor::Unknown => true, DepNodeColor::Red => false, - DepNodeColor::Green(..) => sess.threads().is_some(), // Other threads may mark this green + DepNodeColor::Green(..) => sess.opts.jobs.frontend.is_some(), // Other threads may mark this green }; if !ok { panic!("{}", msg()) diff --git a/compiler/rustc_query_impl/src/execution.rs b/compiler/rustc_query_impl/src/execution.rs index a9192d0417712..350a65d030960 100644 --- a/compiler/rustc_query_impl/src/execution.rs +++ b/compiler/rustc_query_impl/src/execution.rs @@ -300,7 +300,7 @@ fn try_execute_query<'tcx, C: QueryCache, const INCR: bool>( // re-executing the query since `try_start` only checks that the query is not currently // executing, but another thread may have already completed the query and stores it result // in the query cache. - if tcx.sess.threads().is_some() { + if tcx.sess.opts.jobs.frontend.is_some() { if let Some((value, index)) = query.cache.lookup(&key) { tcx.prof.query_cache_hit(index.into()); return (value, Some(index)); diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 37488ebbf1e8f..022784b56d4ce 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -7,10 +7,11 @@ use std::collections::btree_map::{ use std::collections::{BTreeMap, BTreeSet}; use std::ffi::OsStr; use std::hash::Hash; +use std::num::NonZero; use std::path::{Path, PathBuf}; use std::str::{self, FromStr}; use std::sync::LazyLock; -use std::{cmp, fs, iter}; +use std::{cmp, fs, iter, thread}; use externs::{ExternOpt, split_extern_opt}; use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; @@ -1473,6 +1474,7 @@ impl Default for Options { verbose: false, target_modifiers: BTreeMap::default(), mitigation_coverage_map: Default::default(), + jobs: Jobs { frontend: None, backend: None, linker: LinkerJobs::Default }, } } } @@ -1647,6 +1649,147 @@ impl PointerAuthOption { } } +#[derive(Clone, Copy)] +pub enum BackendJobs { + /// The number of backend jobs has a static limit. + Limited(NonZero), + /// The number of backend jobs is either unlimited if there's an inherited jobserver, + /// or limited to 32 if there's no inherited jobserver. + /// This variant exists only to preserve the historical behavior. + /// FIXME: Just use `thread::available_parallelism` as the default static limit. + UnlimitedOr32, +} + +impl BackendJobs { + pub fn value(self) -> NonZero { + match self { + BackendJobs::Limited(n) => n, + BackendJobs::UnlimitedOr32 => NonZero::new(32).unwrap(), + } + } +} + +#[derive(Clone, Copy)] +pub enum LinkerJobs { + /// Do not pass anything to the linker, use it's default behavior. + Default, + /// Pass some specific number of jobs to use to the linker. + Explicit(NonZero), +} + +/// `None` for frontend and backend means everything is single-threaded +/// and synchronization can be disabled. +#[derive(Clone, Copy)] +pub struct Jobs { + pub frontend: Option>, + pub backend: Option, + pub linker: LinkerJobs, +} + +fn parse_jobs_all( + early_dcx: &EarlyDiagCtxt, + matches: &getopts::Matches, + zthreads: Option<&str>, + zno_parallel_backend: bool, + unstable: bool, +) -> Jobs { + if zno_parallel_backend { + early_dcx.early_fatal("`-Zno-parallel-backend` is removed, use `--jobs-backend=1` instead"); + } + let mut available = None; + let jobs = matches + .opt_str("jobs") + .map(|s| parse_jobs_one(early_dcx, "--jobs", &s, unstable, &mut available)); + let check_upper_limit = |value: Option<_>, opt_name| { + if let Some(jobs) = jobs + && value.or(NonZero::new(1)) > jobs.or(NonZero::new(1)) + { + early_dcx.early_fatal(format!("`{opt_name}` cannot be larger than `--jobs`")); + } + }; + let frontend = match matches.opt_str("jobs-frontend") { + Some(jobs_frontend) => { + let opt_name = "--jobs-frontend"; + let frontend = + parse_jobs_one(early_dcx, opt_name, &jobs_frontend, unstable, &mut available); + check_upper_limit(frontend, opt_name); + if zthreads.is_some() { + early_dcx.early_fatal("cannot use both `--jobs-frontend` and `-Zthreads`"); + } + frontend + } + None => match zthreads { + Some(zthreads) => { + let opt_name = "-Zthreads"; + let frontend = + parse_jobs_one(early_dcx, opt_name, zthreads, unstable, &mut available); + check_upper_limit(frontend, opt_name); + frontend + } + None => jobs.flatten(), + }, + }; + let backend = match matches.opt_str("jobs-backend") { + Some(jobs_backend) => { + let opt_name = "--jobs-backend"; + let backend = + parse_jobs_one(early_dcx, opt_name, &jobs_backend, unstable, &mut available); + check_upper_limit(backend, opt_name); + backend.map(BackendJobs::Limited) + } + None => match jobs { + Some(n) => n.map(BackendJobs::Limited), + None => Some(BackendJobs::UnlimitedOr32), + }, + }; + let linker = match matches.opt_str("jobs-linker") { + Some(jobs_linker) => { + let opt_name = "--jobs-linker"; + let linker = + parse_jobs_one(early_dcx, opt_name, &jobs_linker, unstable, &mut available); + check_upper_limit(linker, opt_name); + LinkerJobs::Explicit(linker.or(NonZero::new(1)).unwrap()) + } + None => match jobs { + Some(n) => LinkerJobs::Explicit(n.or(NonZero::new(1)).unwrap()), + None => LinkerJobs::Default, // back compat with lld + }, + }; + + Jobs { frontend, backend, linker } +} + +// Parse a string passed to one of the `--jobs` options or `-Zthreads`. +fn parse_jobs_one( + early_dcx: &EarlyDiagCtxt, + opt_name: &str, + s: &str, + unstable: bool, + available: &mut Option, +) -> Option> { + if s == "sync" { + // Enable synchronization overhead for benchmarking despite only using one thread. + if !unstable { + early_dcx.early_fatal(format!("`{opt_name}=sync` requires `-Z unstable-options`")); + } + return NonZero::new(1); + } + // The number of jobs is capped by 255 (`u8::MAX`) to avoid arbitrary large numbers like 999999 + // causing compiler panics (#117638). The limit can be potentially increased, because e.g. + // rustc thread pool supports up to `u16::MAX` threads in theory. + let n = match u8::from_str(s) { + Ok(0) => *available.get_or_insert_with(|| match thread::available_parallelism() { + Ok(n) => u8::try_from(n.get()).unwrap_or(u8::MAX), + Err(_) => 1, + }), + Ok(n) => n, + Err(_) => early_dcx + .early_fatal(format!("`{opt_name}`: expected a number from 0 to 255 or `sync`")), + }; + // `Jobs` uses `usize` for more convenient use, even if the actual values are limited to `u8`. + (n > 1).then_some(NonZero::new(usize::from(n)).unwrap()) +} + pub fn build_configuration(sess: &Session, mut user_cfg: Cfg) -> Cfg { // First disallow some configuration given on the command line cfg::disallow_cfgs(sess, &user_cfg); @@ -1962,6 +2105,31 @@ pub fn rustc_optgroups() -> Vec { "", ), opt(Unstable, Multi, "", "env-set", "Inject an environment variable", "="), + opt(Unstable, Opt, "j", "jobs", "Limit on the number of used parallel jobs", ""), + opt( + Unstable, + Opt, + "", + "jobs-frontend", + "Limit on the number of parallel jobs used by frontend", + "", + ), + opt( + Unstable, + Opt, + "", + "jobs-backend", + "Limit on the number of parallel jobs used by backend", + "", + ), + opt( + Unstable, + Opt, + "", + "jobs-linker", + "Limit on the number of parallel jobs used by linker", + "", + ), ]; options.extend(verbose_only.into_iter().map(|mut opt| { opt.is_verbose_help_only = true; @@ -2568,10 +2736,6 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M cg.codegen_units, ); - if unstable_opts.threads == Some(parse::MAX_THREADS_CAP) { - early_dcx.early_warn(format!("number of threads was capped at {}", parse::MAX_THREADS_CAP)); - } - let incremental = cg.incremental.as_ref().map(PathBuf::from); if cg.profile_generate.enabled() && cg.profile_use.is_some() { @@ -2828,6 +2992,14 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M let verbose = matches.opt_present("verbose") || unstable_opts.verbose_internals; + let jobs = parse_jobs_all( + early_dcx, + matches, + unstable_opts.threads.as_deref(), + unstable_opts.no_parallel_backend, + unstable_opts.unstable_options, + ); + Options { crate_types, optimize: opt_level, @@ -2872,6 +3044,7 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M verbose, target_modifiers: collected_options.target_modifiers, mitigation_coverage_map: collected_options.mitigations, + jobs, } } diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 5b71c0435185a..e96440f280880 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -433,10 +433,9 @@ top_level_options!( /// The (potentially remapped) working directory #[rustc_lint_opt_deny_field_access("use `SourceMap::working_dir` instead of this field")] working_dir: RealFileName [TRACKED], - color: ColorConfig [UNTRACKED], - verbose: bool [TRACKED_NO_CRATE_HASH], + jobs: Jobs [UNTRACKED], } ); @@ -827,7 +826,6 @@ mod desc { pub(crate) const parse_number: &str = "a number"; pub(crate) const parse_opt_number: &str = parse_number; pub(crate) const parse_frame_pointer: &str = "one of `true`/`yes`/`on`, `false`/`no`/`off`, or (with -Zunstable-options) `non-leaf` or `always`"; - pub(crate) const parse_threads: &str = "a number or `sync`"; pub(crate) const parse_time_passes_format: &str = "`text` (default) or `json`"; pub(crate) const parse_passes: &str = "a space-separated list of passes, or `all`"; pub(crate) const parse_panic_strategy: &str = "either `unwind`, `abort`, or `immediate-abort`"; @@ -922,7 +920,6 @@ pub mod parse { use std::str::FromStr; pub(crate) use super::*; - pub(crate) const MAX_THREADS_CAP: usize = 256; /// Ignore the value. Used for removed options where we don't actually want to store /// anything in the session. @@ -1171,25 +1168,6 @@ pub mod parse { } } - pub(crate) fn parse_threads(slot: &mut Option, v: Option<&str>) -> bool { - let Some(s) = v else { return false }; - if s == "sync" { - // Enable synchronization despite only using one thread. - *slot = Some(1); - return true; - } - let n = match s.parse().ok() { - Some(0) => std::thread::available_parallelism().map_or(1, NonZero::::get), - Some(i) => i, - None => return false, - }; - // We want to cap the number of threads here to avoid large numbers like 999999 and compiler panics. - // This solution was suggested here https://github.com/rust-lang/rust/issues/117638#issuecomment-1800925067 - let n = n.min(MAX_THREADS_CAP); - *slot = (n > 1).then_some(n); // Enable synchronization if we're using more than one thread. - true - } - /// Use this for any numeric option that has a static default. pub(crate) fn parse_number(slot: &mut T, v: Option<&str>) -> bool { match v.and_then(|s| s.parse().ok()) { @@ -2668,7 +2646,7 @@ options! { no_link: bool = (false, parse_no_value, [TRACKED], "compile without linking"), no_parallel_backend: bool = (false, parse_no_value, [UNTRACKED], - "run LLVM in non-parallel mode (while keeping codegen-units and ThinLTO)"), + "use `--jobs-backend=1` instead"), no_profiler_runtime: bool = (false, parse_no_value, [TRACKED], "prevent automatic injection of the profiler_builtins crate"), no_steal_thir: bool = (false, parse_bool, [UNTRACKED], @@ -2873,13 +2851,8 @@ written to standard error output)"), #[rustc_lint_opt_deny_field_access("use `Session::lto` instead of this field")] thinlto: Option = (None, parse_opt_bool, [TRACKED], "enable ThinLTO when possible"), - /// We default to None here since we want to behave like - /// a sequential compiler for now. This'll likely be adjusted - /// in the future. Note that -Zthreads=0 is the way to get - /// the num_cpus behavior. - #[rustc_lint_opt_deny_field_access("use `Session::threads` instead of this field")] - threads: Option = (None, parse_threads, [UNTRACKED], - "use a thread pool with N threads"), + threads: Option = (None, parse_opt_string, [UNTRACKED], + "use `--jobs-frontend` instead"), time_llvm_passes: bool = (false, parse_bool, [UNTRACKED], "measure time of each LLVM pass (default: no)"), time_passes: bool = (false, parse_bool, [UNTRACKED], diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index eebead6fc1f47..a6b2ac0ba6eb8 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -1036,15 +1036,6 @@ impl Session { .unwrap_or(self.panic_strategy().unwinds() || self.target.default_uwtable) } - /// Returns the number of threads used for the thread pool. - /// - /// `None` means thread pool is not used and synchronization is disabled. - /// `Some(n)` means synchronization is enabled with `n` worker threads. - #[inline] - pub fn threads(&self) -> Option { - self.opts.unstable_opts.threads - } - /// Returns the number of codegen units that should be used for this /// compilation pub fn codegen_units(&self) -> CodegenUnits { diff --git a/src/doc/rustc-dev-guide/src/backend/debugging.md b/src/doc/rustc-dev-guide/src/backend/debugging.md index eaa9e399a0524..896fa20df26b1 100644 --- a/src/doc/rustc-dev-guide/src/backend/debugging.md +++ b/src/doc/rustc-dev-guide/src/backend/debugging.md @@ -209,7 +209,7 @@ tutorial above): - The `-Z print-llvm-passes` option will print out LLVM optimization passes being run - The `-Z time-llvm-passes` option measures the time of each LLVM pass - The `-Z verify-llvm-ir` option will verify the LLVM IR for correctness -- The `-Z no-parallel-backend` will disable parallel compilation of distinct compilation units +- The `--jobs-backend=1` will disable parallel compilation of distinct compilation units - The `-Z llvm-time-trace` option will output a Chrome profiler compatible JSON file which contains details and timings for LLVM passes. - The `-C llvm-args=-opt-bisect-limit=` option allows for bisecting LLVM optimizations. diff --git a/src/doc/rustc/src/command-line-arguments.md b/src/doc/rustc/src/command-line-arguments.md index b6ee6c3f5fa79..f9e97530214fe 100644 --- a/src/doc/rustc/src/command-line-arguments.md +++ b/src/doc/rustc/src/command-line-arguments.md @@ -481,6 +481,60 @@ with `--error-format=json`. See [the JSON chapter] for more detail. + +## `-j`/`--jobs`, `--jobs-frontend`, `--jobs-backend`, `--jobs-linker`: limit parallelism + +These flags specify the maximum number of parallel jobs used by the compiler, or its specific parts. + +All the options accept a number from 0 to 255, or `sync`. +- `0` is equivalent to the number of available logical CPUs. +- `sync` is equivalent to `1`, but with synchronization overhead enabled (for benchmarking). + +`jobs` is the common upper limit on everything, +more specific `jobs-*` options cannot specify larger values. + +### Frontend parallelism + +Parallelism used by compilation stages from lexing to generation of backend IR (e.g. LLVM IR). + +- If `jobs-frontend` is passed, then it is used as the limit, +- otherwise if `jobs` is passed, then it is used as the limit, +- otherwise `1` is used as the limit (parallelism is disabled), this default may change. + +In any case the parallelism here may be additionally limited dynamically by jobserver +passed from a higher level build system like cargo. + +### Backend parallelism + +Parallelism used by compilation stages converting backend IR to object files. + +- If `jobs-backend` is passed, then it is used as the limit, +- otherwise if `jobs` is passed, then it is used as the limit, +- otherwise `32` is used as the limit or there's no limit in case of an inherited jobserver, + this default may change. + +In any case the parallelism here may be additionally limited dynamically by jobserver +passed from a higher level build system like cargo. + +Note: the backend parallelism limit may currently work incorrectly if `jobs-frontend` or `jobs` +have larger value than `jobs-backend`, or if the inherited jobserver can give a larger number +of tokens. + +### Linker parallelism + +Parallelism used by linker when combining object files into a final binary. + +- If `jobs-linker` is passed, then it is used as the limit, +- otherwise if `jobs` is passed, then it is used as the limit, +- otherwise no options are passed to the linker and its default behavior is used, + this default may change. + +Note: this option is best effort, if the linker doesn't support parallelism, +we cannot enable it, and if the linker uses parallelism by default and doesn't allow limiting it, +then we cannot limit it. + +Currently only LLD supports controlling parallelism. + ## `@path`: load command-line flags from a path diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 770734195ed89..73e3b87b37aa8 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -1576,9 +1576,6 @@ impl<'test> TestCx<'test> { }; compiler.arg(input_file); - // Use a single thread for efficiency and a deterministic error message order - compiler.arg("-Zthreads=1"); - // Hide libstd sources from ui tests to make sure we generate the stderr // output that users will see. // Without this, we may be producing good diagnostics in-tree but users diff --git a/tests/ui/compile-flags/jobs/jobs-fail-conflict.a.stderr b/tests/ui/compile-flags/jobs/jobs-fail-conflict.a.stderr new file mode 100644 index 0000000000000..e0bae7682753e --- /dev/null +++ b/tests/ui/compile-flags/jobs/jobs-fail-conflict.a.stderr @@ -0,0 +1,2 @@ +error: `--jobs-frontend` cannot be larger than `--jobs` + diff --git a/tests/ui/compile-flags/jobs/jobs-fail-conflict.b.stderr b/tests/ui/compile-flags/jobs/jobs-fail-conflict.b.stderr new file mode 100644 index 0000000000000..e0bae7682753e --- /dev/null +++ b/tests/ui/compile-flags/jobs/jobs-fail-conflict.b.stderr @@ -0,0 +1,2 @@ +error: `--jobs-frontend` cannot be larger than `--jobs` + diff --git a/tests/ui/compile-flags/jobs/jobs-fail-conflict.c.stderr b/tests/ui/compile-flags/jobs/jobs-fail-conflict.c.stderr new file mode 100644 index 0000000000000..0b9aba3739910 --- /dev/null +++ b/tests/ui/compile-flags/jobs/jobs-fail-conflict.c.stderr @@ -0,0 +1,2 @@ +error: `--jobs-backend` cannot be larger than `--jobs` + diff --git a/tests/ui/compile-flags/jobs/jobs-fail-conflict.d.stderr b/tests/ui/compile-flags/jobs/jobs-fail-conflict.d.stderr new file mode 100644 index 0000000000000..53a43953aa748 --- /dev/null +++ b/tests/ui/compile-flags/jobs/jobs-fail-conflict.d.stderr @@ -0,0 +1,2 @@ +error: `--jobs-linker` cannot be larger than `--jobs` + diff --git a/tests/ui/compile-flags/jobs/jobs-fail-conflict.e.stderr b/tests/ui/compile-flags/jobs/jobs-fail-conflict.e.stderr new file mode 100644 index 0000000000000..e9dfc87bdb9b6 --- /dev/null +++ b/tests/ui/compile-flags/jobs/jobs-fail-conflict.e.stderr @@ -0,0 +1,2 @@ +error: `-Zthreads` cannot be larger than `--jobs` + diff --git a/tests/ui/compile-flags/jobs/jobs-fail-conflict.f.stderr b/tests/ui/compile-flags/jobs/jobs-fail-conflict.f.stderr new file mode 100644 index 0000000000000..ffdb316c58d97 --- /dev/null +++ b/tests/ui/compile-flags/jobs/jobs-fail-conflict.f.stderr @@ -0,0 +1,2 @@ +error: cannot use both `--jobs-frontend` and `-Zthreads` + diff --git a/tests/ui/compile-flags/jobs/jobs-fail-conflict.g.stderr b/tests/ui/compile-flags/jobs/jobs-fail-conflict.g.stderr new file mode 100644 index 0000000000000..26b75419b2f64 --- /dev/null +++ b/tests/ui/compile-flags/jobs/jobs-fail-conflict.g.stderr @@ -0,0 +1,2 @@ +error: Option 'jobs' given more than once + diff --git a/tests/ui/compile-flags/jobs/jobs-fail-conflict.rs b/tests/ui/compile-flags/jobs/jobs-fail-conflict.rs new file mode 100644 index 0000000000000..0e37d0a41f32f --- /dev/null +++ b/tests/ui/compile-flags/jobs/jobs-fail-conflict.rs @@ -0,0 +1,24 @@ +//@ revisions: a b c d e f g +//@ ignore-parallel-frontend option conflicts +//@ compile-flags: -Z unstable-options + +//@[a] compile-flags: -j 1 --jobs-frontend 2 +//@[b] compile-flags: --jobs 1 --jobs-frontend 2 +//[a,b]~? ERROR `--jobs-frontend` cannot be larger than `--jobs` + +//@[c] compile-flags: --jobs 1 --jobs-backend 2 +//[c]~? ERROR `--jobs-backend` cannot be larger than `--jobs` + +//@[d] compile-flags: --jobs 1 --jobs-linker 2 +//[d]~? ERROR `--jobs-linker` cannot be larger than `--jobs` + +//@[e] compile-flags: --jobs 1 -Zthreads=2 +//[e]~? ERROR `-Zthreads` cannot be larger than `--jobs` + +//@[f] compile-flags: --jobs-frontend 2 -Zthreads=2 +//[f]~? ERROR cannot use both `--jobs-frontend` and `-Zthreads` + +//@[g] compile-flags: --jobs 1 --jobs 2 +//[g]~? RAW Option 'jobs' given more than once + +fn main() {} diff --git a/tests/ui/compile-flags/jobs/jobs-fail-gate.a.stderr b/tests/ui/compile-flags/jobs/jobs-fail-gate.a.stderr new file mode 100644 index 0000000000000..3f1932be460f0 --- /dev/null +++ b/tests/ui/compile-flags/jobs/jobs-fail-gate.a.stderr @@ -0,0 +1,2 @@ +error: the `-Z unstable-options` flag must also be passed to enable the flag `jobs` + diff --git a/tests/ui/compile-flags/jobs/jobs-fail-gate.b.stderr b/tests/ui/compile-flags/jobs/jobs-fail-gate.b.stderr new file mode 100644 index 0000000000000..3f1932be460f0 --- /dev/null +++ b/tests/ui/compile-flags/jobs/jobs-fail-gate.b.stderr @@ -0,0 +1,2 @@ +error: the `-Z unstable-options` flag must also be passed to enable the flag `jobs` + diff --git a/tests/ui/compile-flags/jobs/jobs-fail-gate.c.stderr b/tests/ui/compile-flags/jobs/jobs-fail-gate.c.stderr new file mode 100644 index 0000000000000..3f1932be460f0 --- /dev/null +++ b/tests/ui/compile-flags/jobs/jobs-fail-gate.c.stderr @@ -0,0 +1,2 @@ +error: the `-Z unstable-options` flag must also be passed to enable the flag `jobs` + diff --git a/tests/ui/compile-flags/jobs/jobs-fail-gate.d.stderr b/tests/ui/compile-flags/jobs/jobs-fail-gate.d.stderr new file mode 100644 index 0000000000000..43821cd9ca501 --- /dev/null +++ b/tests/ui/compile-flags/jobs/jobs-fail-gate.d.stderr @@ -0,0 +1,2 @@ +error: the `-Z unstable-options` flag must also be passed to enable the flag `jobs-frontend` + diff --git a/tests/ui/compile-flags/jobs/jobs-fail-gate.e.stderr b/tests/ui/compile-flags/jobs/jobs-fail-gate.e.stderr new file mode 100644 index 0000000000000..1e80502e0c2f1 --- /dev/null +++ b/tests/ui/compile-flags/jobs/jobs-fail-gate.e.stderr @@ -0,0 +1,2 @@ +error: the `-Z unstable-options` flag must also be passed to enable the flag `jobs-backend` + diff --git a/tests/ui/compile-flags/jobs/jobs-fail-gate.f.stderr b/tests/ui/compile-flags/jobs/jobs-fail-gate.f.stderr new file mode 100644 index 0000000000000..ef8367821275e --- /dev/null +++ b/tests/ui/compile-flags/jobs/jobs-fail-gate.f.stderr @@ -0,0 +1,2 @@ +error: the `-Z unstable-options` flag must also be passed to enable the flag `jobs-linker` + diff --git a/tests/ui/compile-flags/jobs/jobs-fail-gate.rs b/tests/ui/compile-flags/jobs/jobs-fail-gate.rs new file mode 100644 index 0000000000000..2359165b2f167 --- /dev/null +++ b/tests/ui/compile-flags/jobs/jobs-fail-gate.rs @@ -0,0 +1,17 @@ +//@ revisions: a b c d e f + +//@[a] compile-flags: -j 2 +//@[b] compile-flags: --jobs 2 +//@[c] compile-flags: --jobs sync +//[a,b,c]~? RAW the `-Z unstable-options` flag must also be passed to enable the flag `jobs` + +//@[d] compile-flags: --jobs-frontend 2 +//[d]~? RAW the `-Z unstable-options` flag must also be passed to enable the flag `jobs-frontend` + +//@[e] compile-flags: --jobs-backend 2 +//[e]~? RAW the `-Z unstable-options` flag must also be passed to enable the flag `jobs-backend` + +//@[f] compile-flags: --jobs-linker 2 +//[f]~? RAW the `-Z unstable-options` flag must also be passed to enable the flag `jobs-linker` + +fn main() {} diff --git a/tests/ui/compile-flags/jobs/jobs-fail-range.a.stderr b/tests/ui/compile-flags/jobs/jobs-fail-range.a.stderr new file mode 100644 index 0000000000000..17be8dd96f304 --- /dev/null +++ b/tests/ui/compile-flags/jobs/jobs-fail-range.a.stderr @@ -0,0 +1,2 @@ +error: `--jobs`: expected a number from 0 to 255 or `sync` + diff --git a/tests/ui/compile-flags/jobs/jobs-fail-range.b.stderr b/tests/ui/compile-flags/jobs/jobs-fail-range.b.stderr new file mode 100644 index 0000000000000..17be8dd96f304 --- /dev/null +++ b/tests/ui/compile-flags/jobs/jobs-fail-range.b.stderr @@ -0,0 +1,2 @@ +error: `--jobs`: expected a number from 0 to 255 or `sync` + diff --git a/tests/ui/compile-flags/jobs/jobs-fail-range.c.stderr b/tests/ui/compile-flags/jobs/jobs-fail-range.c.stderr new file mode 100644 index 0000000000000..17be8dd96f304 --- /dev/null +++ b/tests/ui/compile-flags/jobs/jobs-fail-range.c.stderr @@ -0,0 +1,2 @@ +error: `--jobs`: expected a number from 0 to 255 or `sync` + diff --git a/tests/ui/compile-flags/jobs/jobs-fail-range.d.stderr b/tests/ui/compile-flags/jobs/jobs-fail-range.d.stderr new file mode 100644 index 0000000000000..17be8dd96f304 --- /dev/null +++ b/tests/ui/compile-flags/jobs/jobs-fail-range.d.stderr @@ -0,0 +1,2 @@ +error: `--jobs`: expected a number from 0 to 255 or `sync` + diff --git a/tests/ui/compile-flags/jobs/jobs-fail-range.e.stderr b/tests/ui/compile-flags/jobs/jobs-fail-range.e.stderr new file mode 100644 index 0000000000000..c4adb63615e73 --- /dev/null +++ b/tests/ui/compile-flags/jobs/jobs-fail-range.e.stderr @@ -0,0 +1,2 @@ +error: `--jobs-frontend`: expected a number from 0 to 255 or `sync` + diff --git a/tests/ui/compile-flags/jobs/jobs-fail-range.f.stderr b/tests/ui/compile-flags/jobs/jobs-fail-range.f.stderr new file mode 100644 index 0000000000000..87f207b309124 --- /dev/null +++ b/tests/ui/compile-flags/jobs/jobs-fail-range.f.stderr @@ -0,0 +1,2 @@ +error: `--jobs-backend`: expected a number from 0 to 255 or `sync` + diff --git a/tests/ui/compile-flags/jobs/jobs-fail-range.g.stderr b/tests/ui/compile-flags/jobs/jobs-fail-range.g.stderr new file mode 100644 index 0000000000000..3262a79c28dbe --- /dev/null +++ b/tests/ui/compile-flags/jobs/jobs-fail-range.g.stderr @@ -0,0 +1,2 @@ +error: `--jobs-linker`: expected a number from 0 to 255 or `sync` + diff --git a/tests/ui/compile-flags/jobs/jobs-fail-range.h.stderr b/tests/ui/compile-flags/jobs/jobs-fail-range.h.stderr new file mode 100644 index 0000000000000..4ccc4d38111a3 --- /dev/null +++ b/tests/ui/compile-flags/jobs/jobs-fail-range.h.stderr @@ -0,0 +1,2 @@ +error: `-Zthreads`: expected a number from 0 to 255 or `sync` + diff --git a/tests/ui/compile-flags/jobs/jobs-fail-range.i.stderr b/tests/ui/compile-flags/jobs/jobs-fail-range.i.stderr new file mode 100644 index 0000000000000..73ca9cd7e5e75 --- /dev/null +++ b/tests/ui/compile-flags/jobs/jobs-fail-range.i.stderr @@ -0,0 +1,2 @@ +error: Argument to option 'j' missing + diff --git a/tests/ui/compile-flags/jobs/jobs-fail-range.j.stderr b/tests/ui/compile-flags/jobs/jobs-fail-range.j.stderr new file mode 100644 index 0000000000000..dd96c769759a7 --- /dev/null +++ b/tests/ui/compile-flags/jobs/jobs-fail-range.j.stderr @@ -0,0 +1,4 @@ +error: Argument to option 'jobs' missing + Usage: + -j, --jobs Limit on the number of used parallel jobs + diff --git a/tests/ui/compile-flags/jobs/jobs-fail-range.k.stderr b/tests/ui/compile-flags/jobs/jobs-fail-range.k.stderr new file mode 100644 index 0000000000000..283711f432b5e --- /dev/null +++ b/tests/ui/compile-flags/jobs/jobs-fail-range.k.stderr @@ -0,0 +1,2 @@ +error: unstable option `threads` requires a string (`-Z threads=`) + diff --git a/tests/ui/compile-flags/jobs/jobs-fail-range.l.stderr b/tests/ui/compile-flags/jobs/jobs-fail-range.l.stderr new file mode 100644 index 0000000000000..76f9d7e6b46b9 --- /dev/null +++ b/tests/ui/compile-flags/jobs/jobs-fail-range.l.stderr @@ -0,0 +1,2 @@ +error: `-Zno-parallel-backend` is removed, use `--jobs-backend=1` instead + diff --git a/tests/ui/compile-flags/jobs/jobs-fail-range.rs b/tests/ui/compile-flags/jobs/jobs-fail-range.rs new file mode 100644 index 0000000000000..139509a68c66c --- /dev/null +++ b/tests/ui/compile-flags/jobs/jobs-fail-range.rs @@ -0,0 +1,31 @@ +//@ revisions: a b c d e f g h i j k l +//@ compile-flags: -Z unstable-options + +//@[a] compile-flags: -j 256 +//@[b] compile-flags: -j -1 +//@[c] compile-flags: -j nonsense +//@[d] compile-flags: --jobs nonsense +//[a,b,c,d]~? ERROR `--jobs`: expected a number from 0 to 255 or `sync` + +//@[e] compile-flags: --jobs-frontend nonsense +//[e]~? ERROR `--jobs-frontend`: expected a number from 0 to 255 or `sync` + +//@[f] compile-flags: --jobs-backend nonsense +//[f]~? ERROR `--jobs-backend`: expected a number from 0 to 255 or `sync` + +//@[g] compile-flags: --jobs-linker nonsense +//[g]~? ERROR `--jobs-linker`: expected a number from 0 to 255 or `sync` + +//@[h] compile-flags: -Zthreads=nonsense +//[h]~? ERROR `-Zthreads`: expected a number from 0 to 255 or `sync` + +//@[i] compile-flags: -j +//@[j] compile-flags: --jobs +//@[k] compile-flags: -Zthreads +//@[l] compile-flags: -Zno-parallel-backend +//[i]~? RAW Argument to option 'j' missing +//[j]~? RAW Argument to option 'jobs' missing +//[k]~? ERROR unstable option `threads` requires a string +//[l]~? ERROR `-Zno-parallel-backend` is removed, use `--jobs-backend=1` instead + +fn main() {} diff --git a/tests/ui/compile-flags/jobs/jobs-pass-link.rs b/tests/ui/compile-flags/jobs/jobs-pass-link.rs new file mode 100644 index 0000000000000..70058dfcf7bf6 --- /dev/null +++ b/tests/ui/compile-flags/jobs/jobs-pass-link.rs @@ -0,0 +1,4 @@ +//@ build-pass +//@ compile-flags: -Z unstable-options --jobs-linker 2 + +fn main() {} diff --git a/tests/ui/compile-flags/jobs/jobs-pass.rs b/tests/ui/compile-flags/jobs/jobs-pass.rs new file mode 100644 index 0000000000000..33a300f300051 --- /dev/null +++ b/tests/ui/compile-flags/jobs/jobs-pass.rs @@ -0,0 +1,13 @@ +//@ check-pass +//@ revisions: a b c d e f g +//@ ignore-parallel-frontend option conflicts +//@ compile-flags: -Z unstable-options + +//@[a] compile-flags: -j 0 +//@[b] compile-flags: -j 1 +//@[c] compile-flags: -j 255 +//@[d] compile-flags: --jobs 16 --jobs-frontend 8 --jobs-backend 4 --jobs-linker 2 +//@[e] compile-flags: --jobs 16 -Zthreads=8 --jobs-backend 4 +//@[g] compile-flags: -Zthreads=1 -Zthreads=2 + +fn main() {}