1use std::panic::AssertUnwindSafe;
6
7use hir::{Symbol, import_map::ImportMap, sym};
8use rustc_hash::{FxHashMap, FxHashSet};
9use salsa::{Cancelled, Database};
10
11use crate::{FxIndexMap, RootDatabase, base_db::Crate, symbol_index::SymbolIndex};
12
13#[derive(Debug)]
15pub struct ParallelPrimeCachesProgress {
16 pub crates_currently_indexing: Vec<Symbol>,
18 pub crates_total: usize,
20 pub crates_done: usize,
22 pub work_type: &'static str,
23}
24
25pub fn parallel_prime_caches(
32 db: &RootDatabase,
33 scope: &[Crate],
34 num_worker_threads: usize,
35 cb: &(dyn Fn(ParallelPrimeCachesProgress) + Sync),
36) {
37 if scope.is_empty() {
38 return;
39 }
40 let _p = tracing::info_span!("parallel_prime_caches", scope_size = scope.len()).entered();
41
42 enum ParallelPrimeCacheWorkerProgress {
43 BeginCrateDefMap { crate_id: Crate, crate_name: Symbol },
44 EndCrateDefMap { crate_id: Crate },
45 EndCrateImportMap,
46 EndSema,
47 EndModuleSymbols,
48 Cancelled(Cancelled),
49 }
50
51 let scope_set: FxHashSet<Crate> = scope.iter().copied().collect();
65
66 let (reverse_deps, mut to_be_done_deps) = {
67 let to_be_done_deps = scope
70 .iter()
71 .map(|&krate| {
72 let count = krate
73 .data(db)
74 .dependencies
75 .iter()
76 .filter(|dep| scope_set.contains(&dep.crate_id))
77 .count() as u32;
78 (krate, count)
79 })
80 .collect::<FxHashMap<_, _>>();
81 let mut reverse_deps =
82 scope.iter().map(|&krate| (krate, Vec::new())).collect::<FxHashMap<_, _>>();
83 for &krate in scope {
84 for dep in &krate.data(db).dependencies {
85 if let Some(rev) = reverse_deps.get_mut(&dep.crate_id) {
86 rev.push(krate);
87 }
88 }
89 }
90 (reverse_deps, to_be_done_deps)
91 };
92
93 let (
94 def_map_work_sender,
95 import_map_work_sender,
96 symbols_work_sender,
97 sema_work_sender,
98 progress_receiver,
99 ) = {
100 let (progress_sender, progress_receiver) = crossbeam_channel::unbounded();
101 let (def_map_work_sender, def_map_work_receiver) = crossbeam_channel::unbounded();
102 let (import_map_work_sender, import_map_work_receiver) = crossbeam_channel::unbounded();
103 let (sema_work_sender, sema_work_receiver) = crossbeam_channel::unbounded();
104 let (symbols_work_sender, symbols_work_receiver) = crossbeam_channel::unbounded();
105 let prime_caches_worker = move |db: RootDatabase| {
106 let handle_def_map = |crate_id, crate_name| {
107 progress_sender.send(ParallelPrimeCacheWorkerProgress::BeginCrateDefMap {
108 crate_id,
109 crate_name,
110 })?;
111
112 let cancelled = Cancelled::catch(|| {
113 _ = hir::crate_def_map(&db, crate_id);
114 });
115
116 match cancelled {
117 Ok(()) => progress_sender
118 .send(ParallelPrimeCacheWorkerProgress::EndCrateDefMap { crate_id })?,
119 Err(cancelled) => progress_sender
120 .send(ParallelPrimeCacheWorkerProgress::Cancelled(cancelled))?,
121 }
122
123 Ok::<_, crossbeam_channel::SendError<_>>(())
124 };
125 let handle_sema = |crate_id| {
126 let cancelled = Cancelled::catch(|| {
127 hir::attach_db(&db, || {
128 _ = hir::TraitImpls::for_crate(&db, crate_id);
133 })
134 });
135
136 match cancelled {
137 Ok(()) => progress_sender.send(ParallelPrimeCacheWorkerProgress::EndSema)?,
138 Err(cancelled) => progress_sender
139 .send(ParallelPrimeCacheWorkerProgress::Cancelled(cancelled))?,
140 }
141
142 Ok::<_, crossbeam_channel::SendError<_>>(())
143 };
144 let handle_import_map = |crate_id| {
145 let cancelled = Cancelled::catch(|| _ = ImportMap::of(&db, crate_id));
146
147 match cancelled {
148 Ok(()) => {
149 progress_sender.send(ParallelPrimeCacheWorkerProgress::EndCrateImportMap)?
150 }
151 Err(cancelled) => progress_sender
152 .send(ParallelPrimeCacheWorkerProgress::Cancelled(cancelled))?,
153 }
154
155 Ok::<_, crossbeam_channel::SendError<_>>(())
156 };
157 let handle_symbols = |module: hir::Module| {
158 let cancelled = Cancelled::catch(AssertUnwindSafe(|| {
159 _ = SymbolIndex::module_symbols(&db, module)
160 }));
161
162 match cancelled {
163 Ok(()) => {
164 progress_sender.send(ParallelPrimeCacheWorkerProgress::EndModuleSymbols)?
165 }
166 Err(cancelled) => progress_sender
167 .send(ParallelPrimeCacheWorkerProgress::Cancelled(cancelled))?,
168 }
169
170 Ok::<_, crossbeam_channel::SendError<_>>(())
171 };
172
173 loop {
174 db.unwind_if_revision_cancelled();
175
176 crossbeam_channel::select_biased! {
178 recv(def_map_work_receiver) -> work => {
179 let Ok((crate_id, crate_name)) = work else { break };
180 handle_def_map(crate_id, crate_name)?;
181 }
182 recv(sema_work_receiver) -> work => {
183 let Ok(crate_id) = work else { break };
184 handle_sema(crate_id)?;
185 }
186 recv(import_map_work_receiver) -> work => {
187 let Ok(crate_id) = work else { break };
188 handle_import_map(crate_id)?;
189 }
190 recv(symbols_work_receiver) -> work => {
191 let Ok(module) = work else { break };
192 handle_symbols(module)?;
193 }
194 }
195 }
196 Ok::<_, crossbeam_channel::SendError<_>>(())
197 };
198
199 for id in 0..num_worker_threads {
200 stdx::thread::Builder::new(
201 stdx::thread::ThreadIntent::Worker,
202 format!("PrimeCaches#{id}"),
203 )
204 .allow_leak(true)
205 .spawn({
206 let worker = prime_caches_worker.clone();
207 let db = db.clone();
208 move || worker(db)
209 })
210 .expect("failed to spawn thread");
211 }
212
213 (
214 def_map_work_sender,
215 import_map_work_sender,
216 symbols_work_sender,
217 sema_work_sender,
218 progress_receiver,
219 )
220 };
221
222 let crate_def_maps_total = scope.len();
223 let mut crate_def_maps_done = 0;
224 let (mut crate_import_maps_total, mut crate_import_maps_done) = (0usize, 0usize);
225 let (mut module_symbols_total, mut module_symbols_done) = (0usize, 0usize);
226 let (mut sema_total, mut sema_done) = (0usize, 0usize);
227
228 let mut crates_currently_indexing =
231 FxIndexMap::with_capacity_and_hasher(num_worker_threads, Default::default());
232
233 for (&krate, &to_be_done_deps) in &to_be_done_deps {
234 if to_be_done_deps != 0 {
235 continue;
236 }
237
238 let name = crate_name(db, krate);
239 def_map_work_sender.send((krate, name)).ok();
240 }
241
242 while crate_def_maps_done < crate_def_maps_total
243 || crate_import_maps_done < crate_import_maps_total
244 || module_symbols_done < module_symbols_total
245 || sema_done < sema_total
246 {
247 db.unwind_if_revision_cancelled();
248
249 let progress = ParallelPrimeCachesProgress {
250 crates_currently_indexing: crates_currently_indexing.values().cloned().collect(),
251 crates_done: crate_def_maps_done,
252 crates_total: crate_def_maps_total,
253 work_type: "Indexing",
254 };
255
256 cb(progress);
257
258 let progress = match progress_receiver.recv() {
260 Ok(p) => p,
261 Err(crossbeam_channel::RecvError) => {
262 cb(ParallelPrimeCachesProgress {
264 crates_currently_indexing: vec![],
265 crates_done: crate_def_maps_done,
266 crates_total: crate_def_maps_done,
267 work_type: "Done",
268 });
269 return;
270 }
271 };
272
273 match progress {
274 ParallelPrimeCacheWorkerProgress::BeginCrateDefMap { crate_id, crate_name } => {
275 crates_currently_indexing.insert(crate_id, crate_name);
276 }
277 ParallelPrimeCacheWorkerProgress::EndCrateDefMap { crate_id } => {
278 crates_currently_indexing.swap_remove(&crate_id);
279 crate_def_maps_done += 1;
280
281 for &dep in &reverse_deps[&crate_id] {
283 let to_be_done = to_be_done_deps.get_mut(&dep).unwrap();
284 *to_be_done -= 1;
285 if *to_be_done == 0 {
286 let dep_name = crate_name(db, dep);
287 def_map_work_sender.send((dep, dep_name)).ok();
288 }
289 }
290
291 if crate_def_maps_done == crate_def_maps_total {
292 cb(ParallelPrimeCachesProgress {
293 crates_currently_indexing: vec![],
294 crates_done: crate_def_maps_done,
295 crates_total: crate_def_maps_done,
296 work_type: "Collecting Symbols",
297 });
298 }
299
300 sema_work_sender.send(crate_id).ok();
301 sema_total += 1;
302 let origin = &crate_id.data(db).origin;
303 if origin.is_lang() {
304 crate_import_maps_total += 1;
305 import_map_work_sender.send(crate_id).ok();
306 } else if origin.is_local() {
307 let modules = hir::Crate::from(crate_id).modules(db);
317 module_symbols_total += modules.len();
318 for module in modules {
319 symbols_work_sender.send(module).ok();
320 }
321 }
322 }
323 ParallelPrimeCacheWorkerProgress::EndCrateImportMap => crate_import_maps_done += 1,
324 ParallelPrimeCacheWorkerProgress::EndModuleSymbols => module_symbols_done += 1,
325 ParallelPrimeCacheWorkerProgress::EndSema => sema_done += 1,
326 ParallelPrimeCacheWorkerProgress::Cancelled(cancelled) => {
327 std::panic::resume_unwind(Box::new(cancelled));
329 }
330 }
331 }
332}
333
334fn crate_name(db: &RootDatabase, krate: Crate) -> Symbol {
335 krate
336 .extra_data(db)
337 .display_name
338 .as_deref()
339 .cloned()
340 .unwrap_or_else(|| sym::Integer::get(salsa::plumbing::AsId::as_id(&krate).index() as usize))
341}