Unnamed repository; edit this file 'description' to name the repository.
Merge pull request #21385 from Shourya742/2026-01-01-parallelize-proc-macro-expansion
internal: Parallelize proc macro expansion
Laurențiu Nicola 3 months ago
parent 080e703 · parent d379776 · commit c7f14db
-rw-r--r--crates/load-cargo/src/lib.rs30
-rw-r--r--crates/proc-macro-api/src/bidirectional_protocol.rs9
-rw-r--r--crates/proc-macro-api/src/legacy_protocol.rs7
-rw-r--r--crates/proc-macro-api/src/lib.rs64
-rw-r--r--crates/proc-macro-api/src/pool.rs91
-rw-r--r--crates/proc-macro-api/src/process.rs59
-rw-r--r--crates/rust-analyzer/src/cli/analysis_stats.rs1
-rw-r--r--crates/rust-analyzer/src/cli/diagnostics.rs1
-rw-r--r--crates/rust-analyzer/src/cli/lsif.rs1
-rw-r--r--crates/rust-analyzer/src/cli/prime_caches.rs1
-rw-r--r--crates/rust-analyzer/src/cli/run_tests.rs1
-rw-r--r--crates/rust-analyzer/src/cli/rustc_tests.rs1
-rw-r--r--crates/rust-analyzer/src/cli/scip.rs1
-rw-r--r--crates/rust-analyzer/src/cli/ssr.rs2
-rw-r--r--crates/rust-analyzer/src/cli/unresolved_references.rs1
-rw-r--r--crates/rust-analyzer/src/config.rs37
-rw-r--r--crates/rust-analyzer/src/integrated_benchmarks.rs3
-rw-r--r--crates/rust-analyzer/src/reload.rs20
-rw-r--r--docs/book/src/configuration_generated.md10
-rw-r--r--editors/code/package.json25
20 files changed, 288 insertions, 77 deletions
diff --git a/crates/load-cargo/src/lib.rs b/crates/load-cargo/src/lib.rs
index 8342492a33..c2935d94a8 100644
--- a/crates/load-cargo/src/lib.rs
+++ b/crates/load-cargo/src/lib.rs
@@ -45,6 +45,7 @@ pub struct LoadCargoConfig {
pub load_out_dirs_from_check: bool,
pub with_proc_macro_server: ProcMacroServerChoice,
pub prefill_caches: bool,
+ pub proc_macro_processes: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -113,15 +114,25 @@ pub fn load_workspace_into_db(
let proc_macro_server = match &load_config.with_proc_macro_server {
ProcMacroServerChoice::Sysroot => ws.find_sysroot_proc_macro_srv().map(|it| {
it.and_then(|it| {
- ProcMacroClient::spawn(&it, extra_env, ws.toolchain.as_ref()).map_err(Into::into)
+ ProcMacroClient::spawn(
+ &it,
+ extra_env,
+ ws.toolchain.as_ref(),
+ load_config.proc_macro_processes,
+ )
+ .map_err(Into::into)
})
.map_err(|e| ProcMacroLoadingError::ProcMacroSrvError(e.to_string().into_boxed_str()))
}),
- ProcMacroServerChoice::Explicit(path) => {
- Some(ProcMacroClient::spawn(path, extra_env, ws.toolchain.as_ref()).map_err(|e| {
- ProcMacroLoadingError::ProcMacroSrvError(e.to_string().into_boxed_str())
- }))
- }
+ ProcMacroServerChoice::Explicit(path) => Some(
+ ProcMacroClient::spawn(
+ path,
+ extra_env,
+ ws.toolchain.as_ref(),
+ load_config.proc_macro_processes,
+ )
+ .map_err(|e| ProcMacroLoadingError::ProcMacroSrvError(e.to_string().into_boxed_str())),
+ ),
ProcMacroServerChoice::None => Some(Err(ProcMacroLoadingError::Disabled)),
};
match &proc_macro_server {
@@ -435,7 +446,7 @@ pub fn load_proc_macro(
) -> ProcMacroLoadResult {
let res: Result<Vec<_>, _> = (|| {
let dylib = MacroDylib::new(path.to_path_buf());
- let vec = server.load_dylib(dylib, Some(&mut reject_subrequests)).map_err(|e| {
+ let vec = server.load_dylib(dylib, Some(&reject_subrequests)).map_err(|e| {
ProcMacroLoadingError::ProcMacroSrvError(format!("{e}").into_boxed_str())
})?;
if vec.is_empty() {
@@ -541,7 +552,7 @@ impl ProcMacroExpander for Expander {
mixed_site: Span,
current_dir: String,
) -> Result<tt::TopSubtree, ProcMacroExpansionError> {
- let mut cb = |req| match req {
+ let cb = |req| match req {
SubRequest::LocalFilePath { file_id } => {
let file_id = FileId::from_raw(file_id);
let source_root_id = db.file_source_root(file_id).source_root_id(db);
@@ -613,7 +624,7 @@ impl ProcMacroExpander for Expander {
call_site,
mixed_site,
current_dir,
- Some(&mut cb),
+ Some(&cb),
) {
Ok(Ok(subtree)) => Ok(subtree),
Ok(Err(err)) => Err(ProcMacroExpansionError::Panic(err)),
@@ -657,6 +668,7 @@ mod tests {
load_out_dirs_from_check: false,
with_proc_macro_server: ProcMacroServerChoice::None,
prefill_caches: false,
+ proc_macro_processes: 1,
};
let (db, _vfs, _proc_macro) =
load_workspace_at(path, &cargo_config, &load_cargo_config, &|_| {}).unwrap();
diff --git a/crates/proc-macro-api/src/bidirectional_protocol.rs b/crates/proc-macro-api/src/bidirectional_protocol.rs
index 5996f88298..b5f43e1d37 100644
--- a/crates/proc-macro-api/src/bidirectional_protocol.rs
+++ b/crates/proc-macro-api/src/bidirectional_protocol.rs
@@ -28,7 +28,7 @@ use crate::{
pub mod msg;
-pub type SubCallback<'a> = &'a mut dyn FnMut(SubRequest) -> Result<SubResponse, ServerError>;
+pub type SubCallback<'a> = &'a dyn Fn(SubRequest) -> Result<SubResponse, ServerError>;
pub fn run_conversation<C: Codec>(
writer: &mut dyn Write,
@@ -138,6 +138,7 @@ pub(crate) fn find_proc_macros(
pub(crate) fn expand(
proc_macro: &ProcMacro,
+ process: &ProcMacroServerProcess,
subtree: tt::SubtreeView<'_>,
attr: Option<tt::SubtreeView<'_>>,
env: Vec<(String, String)>,
@@ -147,7 +148,7 @@ pub(crate) fn expand(
current_dir: String,
callback: SubCallback<'_>,
) -> Result<Result<tt::TopSubtree, String>, crate::ServerError> {
- let version = proc_macro.process.version();
+ let version = process.version();
let mut span_data_table = SpanDataIndexMap::default();
let def_site = span_data_table.insert_full(def_site).0;
let call_site = span_data_table.insert_full(call_site).0;
@@ -164,7 +165,7 @@ pub(crate) fn expand(
call_site,
mixed_site,
},
- span_data_table: if proc_macro.process.rust_analyzer_spans() {
+ span_data_table: if process.rust_analyzer_spans() {
serialize_span_data_index_map(&span_data_table)
} else {
Vec::new()
@@ -175,7 +176,7 @@ pub(crate) fn expand(
current_dir: Some(current_dir),
})));
- let response_payload = run_request(&proc_macro.process, task, callback)?;
+ let response_payload = run_request(process, task, callback)?;
match response_payload {
BidirectionalMessage::Response(Response::ExpandMacro(it)) => Ok(it
diff --git a/crates/proc-macro-api/src/legacy_protocol.rs b/crates/proc-macro-api/src/legacy_protocol.rs
index aabe5a0118..eedf66d460 100644
--- a/crates/proc-macro-api/src/legacy_protocol.rs
+++ b/crates/proc-macro-api/src/legacy_protocol.rs
@@ -77,6 +77,7 @@ pub(crate) fn find_proc_macros(
pub(crate) fn expand(
proc_macro: &ProcMacro,
+ process: &ProcMacroServerProcess,
subtree: tt::SubtreeView<'_>,
attr: Option<tt::SubtreeView<'_>>,
env: Vec<(String, String)>,
@@ -85,7 +86,7 @@ pub(crate) fn expand(
mixed_site: Span,
current_dir: String,
) -> Result<Result<tt::TopSubtree, String>, crate::ServerError> {
- let version = proc_macro.process.version();
+ let version = process.version();
let mut span_data_table = SpanDataIndexMap::default();
let def_site = span_data_table.insert_full(def_site).0;
let call_site = span_data_table.insert_full(call_site).0;
@@ -102,7 +103,7 @@ pub(crate) fn expand(
call_site,
mixed_site,
},
- span_data_table: if proc_macro.process.rust_analyzer_spans() {
+ span_data_table: if process.rust_analyzer_spans() {
serialize_span_data_index_map(&span_data_table)
} else {
Vec::new()
@@ -113,7 +114,7 @@ pub(crate) fn expand(
current_dir: Some(current_dir),
};
- let response = send_task(&proc_macro.process, Request::ExpandMacro(Box::new(task)))?;
+ let response = send_task(process, Request::ExpandMacro(Box::new(task)))?;
match response {
Response::ExpandMacro(it) => Ok(it
diff --git a/crates/proc-macro-api/src/lib.rs b/crates/proc-macro-api/src/lib.rs
index 01195c10fe..3acd0b292a 100644
--- a/crates/proc-macro-api/src/lib.rs
+++ b/crates/proc-macro-api/src/lib.rs
@@ -18,6 +18,7 @@ extern crate rustc_driver as _;
pub mod bidirectional_protocol;
pub mod legacy_protocol;
+pub mod pool;
pub mod process;
pub mod transport;
@@ -27,7 +28,9 @@ use span::{ErasedFileAstId, FIXUP_ERASED_FILE_AST_ID_MARKER, Span};
use std::{fmt, io, sync::Arc, time::SystemTime};
pub use crate::transport::codec::Codec;
-use crate::{bidirectional_protocol::SubCallback, process::ProcMacroServerProcess};
+use crate::{
+ bidirectional_protocol::SubCallback, pool::ProcMacroServerPool, process::ProcMacroServerProcess,
+};
/// The versions of the server protocol
pub mod version {
@@ -85,7 +88,7 @@ pub struct ProcMacroClient {
///
/// That means that concurrent salsa requests may block each other when expanding proc macros,
/// which is unfortunate, but simple and good enough for the time being.
- process: Arc<ProcMacroServerProcess>,
+ pool: Arc<ProcMacroServerPool>,
path: AbsPathBuf,
}
@@ -107,7 +110,7 @@ impl MacroDylib {
/// we share a single expander process for all macros within a workspace.
#[derive(Debug, Clone)]
pub struct ProcMacro {
- process: Arc<ProcMacroServerProcess>,
+ pool: ProcMacroServerPool,
dylib_path: Arc<AbsPathBuf>,
name: Box<str>,
kind: ProcMacroKind,
@@ -121,7 +124,6 @@ impl PartialEq for ProcMacro {
&& self.kind == other.kind
&& self.dylib_path == other.dylib_path
&& self.dylib_last_modified == other.dylib_last_modified
- && Arc::ptr_eq(&self.process, &other.process)
}
}
@@ -151,9 +153,17 @@ impl ProcMacroClient {
Item = (impl AsRef<std::ffi::OsStr>, &'a Option<impl 'a + AsRef<std::ffi::OsStr>>),
> + Clone,
version: Option<&Version>,
+ num_process: usize,
) -> io::Result<ProcMacroClient> {
- let process = ProcMacroServerProcess::spawn(process_path, env, version)?;
- Ok(ProcMacroClient { process: Arc::new(process), path: process_path.to_owned() })
+ let pool_size = num_process;
+ let mut workers = Vec::with_capacity(pool_size);
+ for _ in 0..pool_size {
+ let worker = ProcMacroServerProcess::spawn(process_path, env.clone(), version)?;
+ workers.push(worker);
+ }
+
+ let pool = ProcMacroServerPool::new(workers);
+ Ok(ProcMacroClient { pool: Arc::new(pool), path: process_path.to_owned() })
}
/// Invokes `spawn` and returns a client connected to the resulting read and write handles.
@@ -167,11 +177,20 @@ impl ProcMacroClient {
Box<dyn process::ProcessExit>,
Box<dyn io::Write + Send + Sync>,
Box<dyn io::BufRead + Send + Sync>,
- )>,
+ )> + Clone,
version: Option<&Version>,
+ num_process: usize,
) -> io::Result<ProcMacroClient> {
- let process = ProcMacroServerProcess::run(spawn, version, || "<unknown>".to_owned())?;
- Ok(ProcMacroClient { process: Arc::new(process), path: process_path.to_owned() })
+ let pool_size = num_process;
+ let mut workers = Vec::with_capacity(pool_size);
+ for _ in 0..pool_size {
+ let worker =
+ ProcMacroServerProcess::run(spawn.clone(), version, || "<unknown>".to_owned())?;
+ workers.push(worker);
+ }
+
+ let pool = ProcMacroServerPool::new(workers);
+ Ok(ProcMacroClient { pool: Arc::new(pool), path: process_path.to_owned() })
}
/// Returns the absolute path to the proc-macro server.
@@ -185,31 +204,12 @@ impl ProcMacroClient {
dylib: MacroDylib,
callback: Option<SubCallback<'_>>,
) -> Result<Vec<ProcMacro>, ServerError> {
- let _p = tracing::info_span!("ProcMacroServer::load_dylib").entered();
- let macros = self.process.find_proc_macros(&dylib.path, callback)?;
-
- let dylib_path = Arc::new(dylib.path);
- let dylib_last_modified = std::fs::metadata(dylib_path.as_path())
- .ok()
- .and_then(|metadata| metadata.modified().ok());
- match macros {
- Ok(macros) => Ok(macros
- .into_iter()
- .map(|(name, kind)| ProcMacro {
- process: self.process.clone(),
- name: name.into(),
- kind,
- dylib_path: dylib_path.clone(),
- dylib_last_modified,
- })
- .collect()),
- Err(message) => Err(ServerError { message, io: None }),
- }
+ self.pool.load_dylib(&dylib, callback)
}
/// Checks if the proc-macro server has exited.
pub fn exited(&self) -> Option<&ServerError> {
- self.process.exited()
+ self.pool.exited()
}
}
@@ -225,7 +225,7 @@ impl ProcMacro {
}
fn needs_fixup_change(&self) -> bool {
- let version = self.process.version();
+ let version = self.pool.version();
(version::RUST_ANALYZER_SPAN_SUPPORT..version::HASHED_AST_ID).contains(&version)
}
@@ -269,7 +269,7 @@ impl ProcMacro {
}
}
- self.process.expand(
+ self.pool.pick_process()?.expand(
self,
subtree,
attr,
diff --git a/crates/proc-macro-api/src/pool.rs b/crates/proc-macro-api/src/pool.rs
new file mode 100644
index 0000000000..a637bc0e48
--- /dev/null
+++ b/crates/proc-macro-api/src/pool.rs
@@ -0,0 +1,91 @@
+//! A pool of proc-macro server processes
+use std::sync::Arc;
+
+use crate::{
+ MacroDylib, ProcMacro, ServerError, bidirectional_protocol::SubCallback,
+ process::ProcMacroServerProcess,
+};
+
+#[derive(Debug, Clone)]
+pub(crate) struct ProcMacroServerPool {
+ workers: Arc<[ProcMacroServerProcess]>,
+ version: u32,
+}
+
+impl ProcMacroServerPool {
+ pub(crate) fn new(workers: Vec<ProcMacroServerProcess>) -> Self {
+ let version = workers[0].version();
+ Self { workers: workers.into(), version }
+ }
+}
+
+impl ProcMacroServerPool {
+ pub(crate) fn exited(&self) -> Option<&ServerError> {
+ for worker in &*self.workers {
+ worker.exited()?;
+ }
+ self.workers[0].exited()
+ }
+
+ pub(crate) fn pick_process(&self) -> Result<&ProcMacroServerProcess, ServerError> {
+ let mut best: Option<&ProcMacroServerProcess> = None;
+ let mut best_load = u32::MAX;
+
+ for w in self.workers.iter().filter(|w| w.exited().is_none()) {
+ let load = w.number_of_active_req();
+
+ if load == 0 {
+ return Ok(w);
+ }
+
+ if load < best_load {
+ best = Some(w);
+ best_load = load;
+ }
+ }
+
+ best.ok_or_else(|| ServerError {
+ message: "all proc-macro server workers have exited".into(),
+ io: None,
+ })
+ }
+
+ pub(crate) fn load_dylib(
+ &self,
+ dylib: &MacroDylib,
+ callback: Option<SubCallback<'_>>,
+ ) -> Result<Vec<ProcMacro>, ServerError> {
+ let _span = tracing::info_span!("ProcMacroServer::load_dylib").entered();
+
+ let dylib_path = Arc::new(dylib.path.clone());
+ let dylib_last_modified =
+ std::fs::metadata(dylib_path.as_path()).ok().and_then(|m| m.modified().ok());
+
+ let (first, rest) = self.workers.split_first().expect("worker pool must not be empty");
+
+ let macros = first
+ .find_proc_macros(&dylib.path, callback)?
+ .map_err(|e| ServerError { message: e, io: None })?;
+
+ for worker in rest {
+ worker
+ .find_proc_macros(&dylib.path, callback)?
+ .map_err(|e| ServerError { message: e, io: None })?;
+ }
+
+ Ok(macros
+ .into_iter()
+ .map(|(name, kind)| ProcMacro {
+ pool: self.clone(),
+ name: name.into(),
+ kind,
+ dylib_path: dylib_path.clone(),
+ dylib_last_modified,
+ })
+ .collect())
+ }
+
+ pub(crate) fn version(&self) -> u32 {
+ self.version
+ }
+}
diff --git a/crates/proc-macro-api/src/process.rs b/crates/proc-macro-api/src/process.rs
index cd387dad0d..2f5bef69ab 100644
--- a/crates/proc-macro-api/src/process.rs
+++ b/crates/proc-macro-api/src/process.rs
@@ -1,10 +1,14 @@
//! Handle process life-time and message passing for proc-macro client
use std::{
+ fmt::Debug,
io::{self, BufRead, BufReader, Read, Write},
panic::AssertUnwindSafe,
process::{Child, ChildStdin, ChildStdout, Command, Stdio},
- sync::{Arc, Mutex, OnceLock},
+ sync::{
+ Arc, Mutex, OnceLock,
+ atomic::{AtomicU32, Ordering},
+ },
};
use paths::AbsPath;
@@ -28,6 +32,7 @@ pub(crate) struct ProcMacroServerProcess {
protocol: Protocol,
/// Populated when the server exits.
exited: OnceLock<AssertUnwindSafe<ServerError>>,
+ active: AtomicU32,
}
impl std::fmt::Debug for ProcMacroServerProcess {
@@ -74,7 +79,7 @@ impl ProcessExit for Process {
}
/// Maintains the state of the proc-macro server process.
-struct ProcessSrvState {
+pub(crate) struct ProcessSrvState {
process: Box<dyn ProcessExit>,
stdin: Box<dyn Write + Send + Sync>,
stdout: Box<dyn BufRead + Send + Sync>,
@@ -158,11 +163,12 @@ impl ProcMacroServerProcess {
}
},
exited: OnceLock::new(),
+ active: AtomicU32::new(0),
})
};
let mut srv = create_srv()?;
tracing::info!("sending proc-macro server version check");
- match srv.version_check(Some(&mut reject_subrequests)) {
+ match srv.version_check(Some(&reject_subrequests)) {
Ok(v) if v > version::CURRENT_API_VERSION => {
let process_version = binary_server_version();
err = Some(io::Error::other(format!(
@@ -176,7 +182,7 @@ impl ProcMacroServerProcess {
srv.version = v;
if srv.version >= version::RUST_ANALYZER_SPAN_SUPPORT
&& let Ok(new_mode) =
- srv.enable_rust_analyzer_spans(Some(&mut reject_subrequests))
+ srv.enable_rust_analyzer_spans(Some(&reject_subrequests))
{
match &mut srv.protocol {
Protocol::LegacyJson { mode }
@@ -197,6 +203,22 @@ impl ProcMacroServerProcess {
Err(err.unwrap())
}
+ /// Finds proc-macros in a given dynamic library.
+ pub(crate) fn find_proc_macros(
+ &self,
+ dylib_path: &AbsPath,
+ callback: Option<SubCallback<'_>>,
+ ) -> Result<Result<Vec<(String, ProcMacroKind)>, String>, ServerError> {
+ match self.protocol {
+ Protocol::LegacyJson { .. } => legacy_protocol::find_proc_macros(self, dylib_path),
+
+ Protocol::BidirectionalPostcardPrototype { .. } => {
+ let cb = callback.expect("callback required for bidirectional protocol");
+ bidirectional_protocol::find_proc_macros(self, dylib_path, cb)
+ }
+ }
+ }
+
/// Returns the server error if the process has exited.
pub(crate) fn exited(&self) -> Option<&ServerError> {
self.exited.get().map(|it| &it.0)
@@ -240,21 +262,6 @@ impl ProcMacroServerProcess {
}
}
- /// Finds proc-macros in a given dynamic library.
- pub(crate) fn find_proc_macros(
- &self,
- dylib_path: &AbsPath,
- callback: Option<SubCallback<'_>>,
- ) -> Result<Result<Vec<(String, ProcMacroKind)>, String>, ServerError> {
- match self.protocol {
- Protocol::LegacyJson { .. } => legacy_protocol::find_proc_macros(self, dylib_path),
- Protocol::BidirectionalPostcardPrototype { .. } => {
- let cb = callback.expect("callback required for bidirectional protocol");
- bidirectional_protocol::find_proc_macros(self, dylib_path, cb)
- }
- }
- }
-
pub(crate) fn expand(
&self,
proc_macro: &ProcMacro,
@@ -267,9 +274,11 @@ impl ProcMacroServerProcess {
current_dir: String,
callback: Option<SubCallback<'_>>,
) -> Result<Result<tt::TopSubtree, String>, ServerError> {
- match self.protocol {
+ self.active.fetch_add(1, Ordering::AcqRel);
+ let result = match self.protocol {
Protocol::LegacyJson { .. } => legacy_protocol::expand(
proc_macro,
+ self,
subtree,
attr,
env,
@@ -280,6 +289,7 @@ impl ProcMacroServerProcess {
),
Protocol::BidirectionalPostcardPrototype { .. } => bidirectional_protocol::expand(
proc_macro,
+ self,
subtree,
attr,
env,
@@ -289,7 +299,10 @@ impl ProcMacroServerProcess {
current_dir,
callback.expect("callback required for bidirectional protocol"),
),
- }
+ };
+
+ self.active.fetch_sub(1, Ordering::AcqRel);
+ result
}
pub(crate) fn send_task<Request, Response, C: Codec>(
@@ -348,6 +361,10 @@ impl ProcMacroServerProcess {
bidirectional_protocol::run_conversation::<C>(writer, reader, buf, initial, callback)
})
}
+
+ pub(crate) fn number_of_active_req(&self) -> u32 {
+ self.active.load(Ordering::Acquire)
+ }
}
/// Manages the execution of the proc-macro server process.
diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs
index a02d1a7856..1995d38898 100644
--- a/crates/rust-analyzer/src/cli/analysis_stats.rs
+++ b/crates/rust-analyzer/src/cli/analysis_stats.rs
@@ -91,6 +91,7 @@ impl flags::AnalysisStats {
}
},
prefill_caches: false,
+ proc_macro_processes: 1,
};
let build_scripts_time = if self.disable_build_scripts {
diff --git a/crates/rust-analyzer/src/cli/diagnostics.rs b/crates/rust-analyzer/src/cli/diagnostics.rs
index 776069f155..575c77f842 100644
--- a/crates/rust-analyzer/src/cli/diagnostics.rs
+++ b/crates/rust-analyzer/src/cli/diagnostics.rs
@@ -41,6 +41,7 @@ impl flags::Diagnostics {
load_out_dirs_from_check: !self.disable_build_scripts,
with_proc_macro_server,
prefill_caches: false,
+ proc_macro_processes: 1,
};
let (db, _vfs, _proc_macro) =
load_workspace_at(&self.path, &cargo_config, &load_cargo_config, &|_| {})?;
diff --git a/crates/rust-analyzer/src/cli/lsif.rs b/crates/rust-analyzer/src/cli/lsif.rs
index f3b0699d55..e5e238db63 100644
--- a/crates/rust-analyzer/src/cli/lsif.rs
+++ b/crates/rust-analyzer/src/cli/lsif.rs
@@ -293,6 +293,7 @@ impl flags::Lsif {
load_out_dirs_from_check: true,
with_proc_macro_server: ProcMacroServerChoice::Sysroot,
prefill_caches: false,
+ proc_macro_processes: 1,
};
let path = AbsPathBuf::assert_utf8(env::current_dir()?.join(self.path));
let root = ProjectManifest::discover_single(&path)?;
diff --git a/crates/rust-analyzer/src/cli/prime_caches.rs b/crates/rust-analyzer/src/cli/prime_caches.rs
index 467d8a5388..d5da679179 100644
--- a/crates/rust-analyzer/src/cli/prime_caches.rs
+++ b/crates/rust-analyzer/src/cli/prime_caches.rs
@@ -38,6 +38,7 @@ impl flags::PrimeCaches {
// we want to ensure that this command, not `load_workspace_at`,
// is responsible for that work.
prefill_caches: false,
+ proc_macro_processes: config.proc_macro_num_processes(),
};
let root = AbsPathBuf::assert_utf8(std::env::current_dir()?.join(root));
diff --git a/crates/rust-analyzer/src/cli/run_tests.rs b/crates/rust-analyzer/src/cli/run_tests.rs
index 82ace8c8b3..d4a56d773e 100644
--- a/crates/rust-analyzer/src/cli/run_tests.rs
+++ b/crates/rust-analyzer/src/cli/run_tests.rs
@@ -23,6 +23,7 @@ impl flags::RunTests {
load_out_dirs_from_check: true,
with_proc_macro_server: ProcMacroServerChoice::Sysroot,
prefill_caches: false,
+ proc_macro_processes: 1,
};
let (ref db, _vfs, _proc_macro) =
load_workspace_at(&self.path, &cargo_config, &load_cargo_config, &|_| {})?;
diff --git a/crates/rust-analyzer/src/cli/rustc_tests.rs b/crates/rust-analyzer/src/cli/rustc_tests.rs
index 249566d2ac..e8c6c5f4d4 100644
--- a/crates/rust-analyzer/src/cli/rustc_tests.rs
+++ b/crates/rust-analyzer/src/cli/rustc_tests.rs
@@ -103,6 +103,7 @@ impl Tester {
load_out_dirs_from_check: false,
with_proc_macro_server: ProcMacroServerChoice::Sysroot,
prefill_caches: false,
+ proc_macro_processes: 1,
};
let (db, _vfs, _proc_macro) =
load_workspace(workspace, &cargo_config.extra_env, &load_cargo_config)?;
diff --git a/crates/rust-analyzer/src/cli/scip.rs b/crates/rust-analyzer/src/cli/scip.rs
index 271d2507bc..ed0476697c 100644
--- a/crates/rust-analyzer/src/cli/scip.rs
+++ b/crates/rust-analyzer/src/cli/scip.rs
@@ -52,6 +52,7 @@ impl flags::Scip {
load_out_dirs_from_check: true,
with_proc_macro_server: ProcMacroServerChoice::Sysroot,
prefill_caches: true,
+ proc_macro_processes: config.proc_macro_num_processes(),
};
let cargo_config = config.cargo(None);
let (db, vfs, _) = load_workspace_at(
diff --git a/crates/rust-analyzer/src/cli/ssr.rs b/crates/rust-analyzer/src/cli/ssr.rs
index 3918683145..5c69bda723 100644
--- a/crates/rust-analyzer/src/cli/ssr.rs
+++ b/crates/rust-analyzer/src/cli/ssr.rs
@@ -20,6 +20,7 @@ impl flags::Ssr {
load_out_dirs_from_check: true,
with_proc_macro_server: ProcMacroServerChoice::Sysroot,
prefill_caches: false,
+ proc_macro_processes: 1,
};
let (ref db, vfs, _proc_macro) = load_workspace_at(
&std::env::current_dir()?,
@@ -56,6 +57,7 @@ impl flags::Search {
load_out_dirs_from_check: true,
with_proc_macro_server: ProcMacroServerChoice::Sysroot,
prefill_caches: false,
+ proc_macro_processes: 1,
};
let (ref db, _vfs, _proc_macro) = load_workspace_at(
&std::env::current_dir()?,
diff --git a/crates/rust-analyzer/src/cli/unresolved_references.rs b/crates/rust-analyzer/src/cli/unresolved_references.rs
index 294add682d..49c6fcb91e 100644
--- a/crates/rust-analyzer/src/cli/unresolved_references.rs
+++ b/crates/rust-analyzer/src/cli/unresolved_references.rs
@@ -44,6 +44,7 @@ impl flags::UnresolvedReferences {
load_out_dirs_from_check: !self.disable_build_scripts,
with_proc_macro_server,
prefill_caches: false,
+ proc_macro_processes: config.proc_macro_num_processes(),
};
let (db, vfs, _proc_macro) =
load_workspace_at(&self.path, &cargo_config, &load_cargo_config, &|_| {})?;
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 7064546374..0dda7f3cc2 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -387,6 +387,12 @@ config_data! {
/// Enable support for procedural macros, implies `#rust-analyzer.cargo.buildScripts.enable#`.
procMacro_enable: bool = true,
+ /// Number of proc-macro server processes to spawn.
+ ///
+ /// Controls how many independent `proc-macro-srv` processes rust-analyzer
+ /// runs in parallel to handle macro expansion.
+ procMacro_processes: NumProcesses = NumProcesses::Concrete(1),
+
/// Internal config, path to proc-macro server executable.
procMacro_server: Option<Utf8PathBuf> = None,
@@ -2671,6 +2677,13 @@ impl Config {
}
}
+ pub fn proc_macro_num_processes(&self) -> usize {
+ match self.procMacro_processes() {
+ NumProcesses::Concrete(0) | NumProcesses::Physical => num_cpus::get_physical(),
+ &NumProcesses::Concrete(n) => n,
+ }
+ }
+
pub fn main_loop_num_threads(&self) -> usize {
match self.numThreads() {
Some(NumThreads::Concrete(0)) | None | Some(NumThreads::Physical) => {
@@ -3107,6 +3120,14 @@ pub enum NumThreads {
Concrete(usize),
}
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
+#[serde(rename_all = "snake_case")]
+pub enum NumProcesses {
+ Physical,
+ #[serde(untagged)]
+ Concrete(usize),
+}
+
macro_rules! _default_val {
($default:expr, $ty:ty) => {{
let default_: $ty = $default;
@@ -3933,6 +3954,22 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
},
],
},
+ "NumProcesses" => set! {
+ "anyOf": [
+ {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 255
+ },
+ {
+ "type": "string",
+ "enum": ["physical"],
+ "enumDescriptions": [
+ "Use the number of physical cores",
+ ],
+ },
+ ],
+ },
"Option<NumThreads>" => set! {
"anyOf": [
{
diff --git a/crates/rust-analyzer/src/integrated_benchmarks.rs b/crates/rust-analyzer/src/integrated_benchmarks.rs
index c61825b99f..d16ca2fb48 100644
--- a/crates/rust-analyzer/src/integrated_benchmarks.rs
+++ b/crates/rust-analyzer/src/integrated_benchmarks.rs
@@ -53,6 +53,7 @@ fn integrated_highlighting_benchmark() {
load_out_dirs_from_check: true,
with_proc_macro_server: ProcMacroServerChoice::Sysroot,
prefill_caches: false,
+ proc_macro_processes: 1,
};
let (db, vfs, _proc_macro) = {
@@ -121,6 +122,7 @@ fn integrated_completion_benchmark() {
load_out_dirs_from_check: true,
with_proc_macro_server: ProcMacroServerChoice::Sysroot,
prefill_caches: true,
+ proc_macro_processes: 1,
};
let (db, vfs, _proc_macro) = {
@@ -322,6 +324,7 @@ fn integrated_diagnostics_benchmark() {
load_out_dirs_from_check: true,
with_proc_macro_server: ProcMacroServerChoice::Sysroot,
prefill_caches: true,
+ proc_macro_processes: 1,
};
let (db, vfs, _proc_macro) = {
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index ccafbd7b30..83f4a19b39 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -701,15 +701,19 @@ impl GlobalState {
_ => Default::default(),
};
info!("Using proc-macro server at {path}");
+ let num_process = self.config.proc_macro_num_processes();
- Some(ProcMacroClient::spawn(&path, &env, ws.toolchain.as_ref()).map_err(|err| {
- tracing::error!(
- "Failed to run proc-macro server from path {path}, error: {err:?}",
- );
- anyhow::format_err!(
- "Failed to run proc-macro server from path {path}, error: {err:?}",
- )
- }))
+ Some(
+ ProcMacroClient::spawn(&path, &env, ws.toolchain.as_ref(), num_process)
+ .map_err(|err| {
+ tracing::error!(
+ "Failed to run proc-macro server from path {path}, error: {err:?}",
+ );
+ anyhow::format_err!(
+ "Failed to run proc-macro server from path {path}, error: {err:?}",
+ )
+ }),
+ )
}))
}
diff --git a/docs/book/src/configuration_generated.md b/docs/book/src/configuration_generated.md
index 9bc4126310..8460c2c7d0 100644
--- a/docs/book/src/configuration_generated.md
+++ b/docs/book/src/configuration_generated.md
@@ -1318,6 +1318,16 @@ These proc-macros will be ignored when trying to expand them.
This config takes a map of crate names with the exported proc-macro names to ignore as values.
+## rust-analyzer.procMacro.processes {#procMacro.processes}
+
+Default: `1`
+
+Number of proc-macro server processes to spawn.
+
+Controls how many independent `proc-macro-srv` processes rust-analyzer
+runs in parallel to handle macro expansion.
+
+
## rust-analyzer.procMacro.server {#procMacro.server}
Default: `null`
diff --git a/editors/code/package.json b/editors/code/package.json
index a197b7abd8..fc20597e88 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -2773,6 +2773,31 @@
{
"title": "Proc Macro",
"properties": {
+ "rust-analyzer.procMacro.processes": {
+ "markdownDescription": "Number of proc-macro server processes to spawn.\n\nControls how many independent `proc-macro-srv` processes rust-analyzer\nruns in parallel to handle macro expansion.",
+ "default": 1,
+ "anyOf": [
+ {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 255
+ },
+ {
+ "type": "string",
+ "enum": [
+ "physical"
+ ],
+ "enumDescriptions": [
+ "Use the number of physical cores"
+ ]
+ }
+ ]
+ }
+ }
+ },
+ {
+ "title": "Proc Macro",
+ "properties": {
"rust-analyzer.procMacro.server": {
"markdownDescription": "Internal config, path to proc-macro server executable.",
"default": null,