From fb014496f6fdb93a5b448a7c237adb8b14e33d70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Wed, 16 Sep 2026 12:34:58 +0200 Subject: [PATCH 1/5] Use 2 parallel frontend threads by default on the nightly and dev channel --- compiler/rustc_session/src/config.rs | 29 +++++++++++++++++-- src/bootstrap/src/core/build_steps/compile.rs | 2 ++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 798dcaf969965..66d9d260211b2 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -1395,7 +1395,7 @@ pub fn parse_remap_path_scope( } RemapPathScopeComponents::DOCUMENTATION - }, + } "debuginfo" => RemapPathScopeComponents::DEBUGINFO, "coverage" => RemapPathScopeComponents::COVERAGE, "object" => RemapPathScopeComponents::OBJECT, @@ -1758,9 +1758,34 @@ fn parse_jobs_all( check_upper_limit(frontend, opt_name); frontend } - None => None, // default to 1 thread irrespectively of `jobs` for now + None => { + // Build-time override to opt into a different default for the frontend thread count + if let Some(default_frontend_jobs) = option_env!("CFG_DEFAULT_FRONTEND_JOBS") { + let mut frontend_jobs = parse_jobs_one( + early_dcx, + "CFG_DEFAULT_FRONTEND_JOBS", + default_frontend_jobs, + true, + &mut available, + ); + + // CFG_DEFAULT_FRONTEND_JOBS is a build-time config, so we cannot use + // `check_upper_limit` here. Instead, we cap the frontend jobs to the value of + // --jobs, if it was passed and was smaller than the default frontend jobs + // count. + if let Some(jobs) = jobs + && frontend_jobs.or(NonZero::new(1)) > jobs.or(NonZero::new(1)) + { + frontend_jobs = jobs; + } + frontend_jobs + } else { + None // default to 1 thread irrespectively of `jobs` for now + } + } }, }; + let backend = match matches.opt_str("jobs-backend") { Some(jobs_backend) => { let opt_name = "--jobs-backend"; diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index b1157ef381013..d928466c47f0e 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -1370,6 +1370,8 @@ fn rustc_cargo_env(builder: &Builder<'_>, cargo: &mut Cargo, target: TargetSelec // We want to enable Polonius Alpha and Next Trait Solver by default on nighty cargo.env("CFG_DEFAULT_POLONIUS_NEXT", "1"); cargo.env("CFG_DEFAULT_NEXT_SOLVER_GLOBALLY", "1"); + // Enable 2 threads for the parallel frontend by default on nightly + cargo.env("CFG_DEFAULT_FRONTEND_JOBS", "2"); } // These conditionals represent a tension between three forces: From 52f1a883011603e2951917f57edebfe587c3d88b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 18 Sep 2026 10:32:03 +0200 Subject: [PATCH 2/5] Run UI tests with `-Zthreads=1`, unless overridden --- src/tools/compiletest/src/runtest.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 0bb8a7003589c..a171a96a7cb34 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -1772,6 +1772,26 @@ impl<'test> TestCx<'test> { rustc.arg(dir_opt); }; + if !self.config.parallel_frontend_enabled() { + // If the parallel frontend thread count was not specified, we override + // it to 1, because the tested toolchain might have a different default + // value for the frontend thread count, but we want to run tests in + // compiletest with the sequential frontend for now, until we prepare the testing + // infrastructure of all test suites to support potentially non-deterministic order of + // diagnostics. + + // We can't set this option if the test already specifies the frontend thread count + // though. + if !self + .props + .compile_flags + .iter() + .any(|flag| flag.contains("-Zthreads") || flag.contains("--jobs-frontend")) + { + compiler.arg("-Zthreads=1"); + } + } + match self.config.mode { TestMode::Incremental => { // If we are extracting and matching errors in the new From b13dc9eb570d065ebafc5069ff11ee55c0de4574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 18 Sep 2026 12:51:06 +0200 Subject: [PATCH 3/5] Make `obtain-borrowck` compatible with multiple threads --- tests/ui-fulldeps/obtain-borrowck.rs | 60 ++++++++++------------------ 1 file changed, 21 insertions(+), 39 deletions(-) diff --git a/tests/ui-fulldeps/obtain-borrowck.rs b/tests/ui-fulldeps/obtain-borrowck.rs index d4a74d3b7dec8..450f7074e7a1f 100644 --- a/tests/ui-fulldeps/obtain-borrowck.rs +++ b/tests/ui-fulldeps/obtain-borrowck.rs @@ -26,13 +26,11 @@ extern crate rustc_interface; extern crate rustc_middle; extern crate rustc_session; -use std::cell::RefCell; use std::collections::HashMap; use std::process::ExitCode; -use std::thread_local; +use std::sync::{LazyLock, Mutex}; -use rustc_borrowck::consumers::{self, BodyWithBorrowckFacts, ConsumerOptions}; -use rustc_data_structures::fx::FxHashMap; +use rustc_borrowck::consumers::{self, ConsumerOptions, PoloniusInput}; use rustc_driver::Compilation; use rustc_hir::def::DefKind; use rustc_hir::def_id::LocalDefId; @@ -104,9 +102,9 @@ impl rustc_driver::Callbacks for CompilerCalls { let mut bodies = get_bodies(tcx); bodies.sort_by(|(def_id1, _), (def_id2, _)| def_id1.cmp(def_id2)); println!("Bodies retrieved for:"); - for (def_id, body) in bodies { + for (def_id, facts) in bodies { println!("{}", def_id); - assert!(body.input_facts.unwrap().cfg_edge.len() > 0); + assert!(facts.cfg_edge.len() > 0); } Compilation::Continue @@ -118,47 +116,31 @@ fn override_queries(_session: &Session, local: &mut Providers) { } // Since mir_borrowck does not have access to any other state, we need to use a -// thread-local for storing the obtained MIR bodies. -// -// Note: We are using 'static lifetime here, which is in general unsound. -// Unfortunately, that is the only lifetime allowed here. Our use is safe -// because we cast it back to `'tcx` before using. -thread_local! { - pub static MIR_BODIES: - RefCell>> = - RefCell::new(HashMap::new()); -} +// global variable for storing the obtained MIR bodies. +// Note that we don't use a thread-local variable, because borrowck can run under multiple threads. +pub static MIR_BODIES: LazyLock>>>> = + LazyLock::new(|| Mutex::new(HashMap::new())); fn mir_borrowck<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> ProvidedValue<'tcx> { let opts = ConsumerOptions::PoloniusInputFacts; let bodies_with_facts = consumers::get_bodies_with_borrowck_facts(tcx, def_id, opts); - // SAFETY: The reader casts the 'static lifetime to 'tcx before using it. - let bodies_with_facts: FxHashMap> = - unsafe { std::mem::transmute(bodies_with_facts) }; - MIR_BODIES.with(|state| { - let mut map = state.borrow_mut(); - for (def_id, body_with_facts) in bodies_with_facts { - assert!(map.insert(def_id, body_with_facts).is_none()); - } - }); + let mut map = MIR_BODIES.lock().unwrap(); + for (def_id, body_with_facts) in bodies_with_facts { + assert!(map.insert(def_id, body_with_facts.input_facts).is_none()); + } let mut providers = Providers::default(); rustc_borrowck::provide(&mut providers.queries); let original_mir_borrowck = providers.queries.mir_borrowck; original_mir_borrowck(tcx, def_id) } -/// Pull MIR bodies stored in the thread-local. -fn get_bodies<'tcx>(tcx: TyCtxt<'tcx>) -> Vec<(String, BodyWithBorrowckFacts<'tcx>)> { - MIR_BODIES.with(|state| { - let mut map = state.borrow_mut(); - map.drain() - .map(|(def_id, body)| { - let def_path = tcx.def_path(def_id.to_def_id()); - // SAFETY: For soundness we need to ensure that the bodies have - // the same lifetime (`'tcx`), which they had before they were - // stored in the thread local. - (def_path.to_string_no_crate_verbose(), unsafe { std::mem::transmute(body) }) - }) - .collect() - }) +/// Pull MIR bodies stored in the global variable. +fn get_bodies<'tcx>(tcx: TyCtxt<'tcx>) -> Vec<(String, PoloniusInput)> { + let mut map = MIR_BODIES.lock().unwrap(); + map.drain() + .map(|(def_id, facts)| { + let def_path = tcx.def_path(def_id.to_def_id()); + (def_path.to_string_no_crate_verbose(), *facts.unwrap()) + }) + .collect() } From 98d499940f2d556ee96df2e7d220932183c253db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 18 Sep 2026 16:07:16 +0200 Subject: [PATCH 4/5] Force Clippy tests to use one frontend thread --- src/tools/clippy/tests/compile-test.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tools/clippy/tests/compile-test.rs b/src/tools/clippy/tests/compile-test.rs index 2a5c3f7e3895d..dec456629d324 100644 --- a/src/tools/clippy/tests/compile-test.rs +++ b/src/tools/clippy/tests/compile-test.rs @@ -236,6 +236,9 @@ impl TestContext { // FIXME(#160895): While the new solver is enabled by default on nightly, // we don't want to use it in our tests for now. "-Znext-solver=coherence", + // Run tests with the sequential frontend, for now, because the parallel frontend can + // produce non-deterministic order of diagnostics + "-Zthreads=1", "-Dwarnings", ] .map(OsString::from), From a3a05c24dda70390c8352e50ea041086f465d172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Sat, 19 Sep 2026 13:24:50 +0200 Subject: [PATCH 5/5] Use a single frontend job in run-make tests --- src/tools/run-make-support/src/external_deps/rustc.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tools/run-make-support/src/external_deps/rustc.rs b/src/tools/run-make-support/src/external_deps/rustc.rs index fab7f2098ea1c..0a19c0ff2be74 100644 --- a/src/tools/run-make-support/src/external_deps/rustc.rs +++ b/src/tools/run-make-support/src/external_deps/rustc.rs @@ -110,6 +110,9 @@ impl Rustc { cmd.arg(format!("-Zcodegen-backend={codegen_backend}")); } + // Use a single frontend thread by default, for now + cmd.arg("-Zthreads=1"); + // Automatically default to cross-compilation Self { cmd, target: Some(target()) } }