Skip to content

Commit 0bb49e3

Browse files
brittanyreymeta-codesync[bot]
authored andcommitted
Parallelize cross-library resolution per-module passes
Summary: Make resolve_cross_library_errors parallel Result on XL benchmark: resolve_cross_library_errors ~7.9s -> ~5.0s Reviewed By: martindemello Differential Revision: D107531055 fbshipit-source-id: 743db2131639cd533208d9d5ea202cc19e4830ee
1 parent 5026717 commit 0bb49e3

1 file changed

Lines changed: 138 additions & 30 deletions

File tree

src/cache.rs

Lines changed: 138 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -288,22 +288,32 @@ impl LibraryCache {
288288
func_safety_by_module: &mut HashMap<ModuleName, HashMap<String, FunctionSafetyInfo>>,
289289
globally_safe_funcs: &mut AHashSet<String>,
290290
) -> bool {
291-
let mut to_promote: Vec<(ModuleName, String)> = Vec::new();
292-
for module in &self.modules {
293-
let Some(fs) = func_safety_by_module.get(&module.name) else {
294-
continue;
295-
};
296-
for (func_name, info) in fs {
297-
if can_promote_missing_dep_function(
298-
info,
299-
module_names,
300-
func_safety_by_module,
301-
globally_safe_funcs,
302-
) {
303-
to_promote.push((module.name, func_name.clone()));
304-
}
305-
}
306-
}
291+
let to_promote: Vec<(ModuleName, String)> = {
292+
// Reborrow as shared for parallel access.
293+
let func_safety_by_module = &*func_safety_by_module;
294+
let globally_safe_funcs = &*globally_safe_funcs;
295+
296+
self.modules
297+
.par_iter()
298+
.filter_map(|module| {
299+
func_safety_by_module
300+
.get(&module.name)
301+
.map(|fs| (module.name, fs))
302+
})
303+
.flat_map_iter(|(name, fs)| {
304+
fs.iter()
305+
.filter(move |(_, info)| {
306+
can_promote_missing_dep_function(
307+
info,
308+
module_names,
309+
func_safety_by_module,
310+
globally_safe_funcs,
311+
)
312+
})
313+
.map(move |(func_name, _)| (name, func_name.clone()))
314+
})
315+
.collect()
316+
};
307317

308318
let promoted = !to_promote.is_empty();
309319
for (module_name, func_name) in to_promote {
@@ -327,27 +337,29 @@ impl LibraryCache {
327337
func_safety_by_module: &HashMap<ModuleName, HashMap<String, FunctionSafetyInfo>>,
328338
globally_safe_funcs: &AHashSet<String>,
329339
) -> bool {
330-
let mut cleared = false;
331-
for module in &mut self.modules {
332-
if let CachedSafety::Ok(ref mut safety) = module.safety {
333-
cleared |= retain_unverified_errors(safety, |func_name| {
340+
self.modules
341+
.par_iter_mut()
342+
.map(|module| {
343+
let CachedSafety::Ok(ref mut safety) = module.safety else {
344+
return false;
345+
};
346+
retain_unverified_errors(safety, |func_name| {
334347
is_call_verified_safe_indexed(
335348
func_name,
336349
module_names,
337350
func_safety_by_module,
338351
globally_safe_funcs,
339352
)
340-
});
341-
}
342-
}
343-
cleared
353+
})
354+
})
355+
.reduce(|| false, |any_cleared, cleared| any_cleared || cleared)
344356
}
345357

346358
/// Resolve missing imports against the merged cache and selectively clear
347359
/// false errors using per-function safety verdicts.
348360
pub fn resolve_cross_library_errors(&mut self) {
349361
let module_names: AHashSet<ModuleName> = self.modules.iter().map(|m| m.name).collect();
350-
let mut ambiguous_resolved = self.resolve_ambiguous_imports(&module_names);
362+
let ambiguous_resolved = self.resolve_ambiguous_imports(&module_names);
351363

352364
self.propagate_re_export_safety();
353365

@@ -357,15 +369,15 @@ impl LibraryCache {
357369
.map(|m| (m.name, std::mem::take(&mut m.function_safety)))
358370
.collect();
359371

360-
for module in &mut self.modules {
372+
self.modules.par_iter_mut().for_each(|module| {
361373
if let CachedSafety::Ok(ref mut safety) = module.safety {
362374
resolve_implicit_imports(&mut safety.implicit_imports, &module_names);
363375
}
364376

365-
let from_ambiguous = ambiguous_resolved.remove(&module.name);
377+
let from_ambiguous = ambiguous_resolved.get(&module.name);
366378

367379
if module.missing_imports.is_empty() && from_ambiguous.is_none() {
368-
continue;
380+
return;
369381
}
370382

371383
let mut still_missing: AHashSet<ModuleName> =
@@ -374,7 +386,7 @@ impl LibraryCache {
374386
AHashSet::with_capacity(module.missing_imports.len());
375387

376388
if let Some(from_ambiguous) = from_ambiguous {
377-
resolved_modules.extend(from_ambiguous);
389+
resolved_modules.extend(from_ambiguous.iter().copied());
378390
}
379391

380392
for missing in module.missing_imports.drain() {
@@ -393,7 +405,7 @@ impl LibraryCache {
393405
is_call_verified_safe(func_name, &resolved_modules, &func_safety_by_module)
394406
});
395407
}
396-
}
408+
});
397409

398410
self.upgrade_missing_dep_functions(&module_names, &mut func_safety_by_module);
399411

@@ -804,3 +816,99 @@ impl CachedExports {
804816
self.return_types.extend(other.return_types);
805817
}
806818
}
819+
820+
#[cfg(test)]
821+
mod tests {
822+
use rayon::ThreadPoolBuilder;
823+
824+
use super::*;
825+
826+
#[test]
827+
fn clear_verified_errors_processes_every_module() {
828+
let module_a = ModuleName::from_str("test.module_a");
829+
let module_b = ModuleName::from_str("test.module_b");
830+
831+
let mut cache = LibraryCache {
832+
modules: vec![
833+
CachedModule {
834+
name: module_a,
835+
safety: CachedSafety::Ok(CachedModuleSafety {
836+
errors: vec![CachedError {
837+
kind: ErrorKind::UnknownFunctionCall,
838+
metadata: "helper()".to_owned(),
839+
}],
840+
force_imports_eager_overrides: Vec::new(),
841+
implicit_imports: Vec::new(),
842+
}),
843+
imports: AHashSet::new(),
844+
missing_imports: AHashSet::new(),
845+
ambiguous_imports: AHashSet::new(),
846+
side_effect_imports: AHashSet::new(),
847+
function_safety: HashMap::new(),
848+
},
849+
CachedModule {
850+
name: module_b,
851+
safety: CachedSafety::Ok(CachedModuleSafety {
852+
errors: vec![CachedError {
853+
kind: ErrorKind::UnknownFunctionCall,
854+
metadata: "helper()".to_owned(),
855+
}],
856+
force_imports_eager_overrides: Vec::new(),
857+
implicit_imports: Vec::new(),
858+
}),
859+
imports: AHashSet::new(),
860+
missing_imports: AHashSet::new(),
861+
ambiguous_imports: AHashSet::new(),
862+
side_effect_imports: AHashSet::new(),
863+
function_safety: HashMap::new(),
864+
},
865+
],
866+
exports: CachedExports {
867+
definitions: Vec::new(),
868+
re_exports: Vec::new(),
869+
all: Vec::new(),
870+
return_types: Vec::new(),
871+
},
872+
};
873+
874+
let module_names: AHashSet<ModuleName> = [module_a, module_b].into_iter().collect();
875+
let func_safety_by_module = HashMap::from([
876+
(
877+
module_a,
878+
HashMap::from([(
879+
"helper".to_owned(),
880+
FunctionSafetyInfo::new(FunctionSafety::Safe),
881+
)]),
882+
),
883+
(
884+
module_b,
885+
HashMap::from([(
886+
"helper".to_owned(),
887+
FunctionSafetyInfo::new(FunctionSafety::Safe),
888+
)]),
889+
),
890+
]);
891+
let globally_safe_funcs: AHashSet<String> = ["helper".to_owned()].into_iter().collect();
892+
893+
let cleared = ThreadPoolBuilder::new()
894+
.num_threads(1)
895+
.build()
896+
.expect("should build test thread pool")
897+
.install(|| {
898+
cache.clear_verified_errors(
899+
&module_names,
900+
&func_safety_by_module,
901+
&globally_safe_funcs,
902+
)
903+
});
904+
905+
assert!(
906+
cleared,
907+
"expected at least one verified error to be removed"
908+
);
909+
assert!(
910+
cache.modules.iter().all(CachedModule::is_safe),
911+
"all modules should have their verified errors cleared",
912+
);
913+
}
914+
}

0 commit comments

Comments
 (0)