Salsify the crate graph

I.e. make it not one giant input but multiple, for incrementality and decreased memory usage for Salsa 3 reasons.
This commit is contained in:
Chayim Refael Friedman 2025-01-02 01:45:32 +02:00
parent 44f18c3d05
commit c94e9efbef
108 changed files with 3630 additions and 2512 deletions

View file

@ -233,7 +233,13 @@ impl RootDatabase {
// // SourceDatabase
// base_db::ParseQuery
// base_db::ParseErrorsQuery
// base_db::CrateGraphQuery
// base_db::AllCratesQuery
// base_db::InternUniqueCrateDataQuery
// base_db::InternUniqueCrateDataLookupQuery
// base_db::CrateDataQuery
// base_db::ExtraCrateDataQuery
// base_db::CrateCfgQuery
// base_db::CrateEnvQuery
// base_db::CrateWorkspaceDataQuery
// // SourceDatabaseExt

View file

@ -1,6 +1,6 @@
//! See [`FamousDefs`].
use base_db::{CrateOrigin, LangCrateOrigin, RootQueryDb as _};
use base_db::{CrateOrigin, LangCrateOrigin};
use hir::{Crate, Enum, Function, Macro, Module, ScopeDef, Semantics, Trait};
use crate::RootDatabase;
@ -198,11 +198,10 @@ impl FamousDefs<'_, '_> {
fn find_lang_crate(&self, origin: LangCrateOrigin) -> Option<Crate> {
let krate = self.1;
let db = self.0.db;
let crate_graph = self.0.db.crate_graph();
let res = krate
.dependencies(db)
.into_iter()
.find(|dep| crate_graph[dep.krate.into()].origin == CrateOrigin::Lang(origin))?
.find(|dep| dep.krate.origin(db) == CrateOrigin::Lang(origin))?
.krate;
Some(res)
}

View file

@ -51,9 +51,8 @@ use salsa::Durability;
use std::{fmt, mem::ManuallyDrop};
use base_db::{
query_group::{self},
FileSourceRootInput, FileText, Files, RootQueryDb, SourceDatabase, SourceRoot, SourceRootId,
SourceRootInput, Upcast,
query_group, CrateGraphBuilder, CratesMap, FileSourceRootInput, FileText, Files, RootQueryDb,
SourceDatabase, SourceRoot, SourceRootId, SourceRootInput, Upcast,
};
use hir::{
db::{DefDatabase, ExpandDatabase, HirDatabase},
@ -85,6 +84,7 @@ pub struct RootDatabase {
// compile times of all `ide_*` and downstream crates suffer greatly.
storage: ManuallyDrop<salsa::Storage<Self>>,
files: Arc<Files>,
crates_map: Arc<CratesMap>,
}
impl std::panic::RefUnwindSafe for RootDatabase {}
@ -102,7 +102,11 @@ impl Drop for RootDatabase {
impl Clone for RootDatabase {
fn clone(&self) -> Self {
Self { storage: self.storage.clone(), files: self.files.clone() }
Self {
storage: self.storage.clone(),
files: self.files.clone(),
crates_map: self.crates_map.clone(),
}
}
}
@ -194,6 +198,10 @@ impl SourceDatabase for RootDatabase {
let files = Arc::clone(&self.files);
files.set_file_source_root_with_durability(self, id, source_root_id, durability);
}
fn crates_map(&self) -> Arc<CratesMap> {
self.crates_map.clone()
}
}
impl Default for RootDatabase {
@ -207,8 +215,11 @@ impl RootDatabase {
let mut db = RootDatabase {
storage: ManuallyDrop::new(salsa::Storage::default()),
files: Default::default(),
crates_map: Default::default(),
};
db.set_crate_graph_with_durability(Default::default(), Durability::HIGH);
// This needs to be here otherwise `CrateGraphBuilder` will panic.
db.set_all_crates(Arc::new(Box::new([])));
CrateGraphBuilder::default().set_in_db(&mut db);
db.set_proc_macros_with_durability(Default::default(), Durability::HIGH);
db.set_local_roots_with_durability(Default::default(), Durability::HIGH);
db.set_library_roots_with_durability(Default::default(), Durability::HIGH);
@ -258,7 +269,11 @@ impl RootDatabase {
}
pub fn snapshot(&self) -> Self {
Self { storage: self.storage.clone(), files: self.files.clone() }
Self {
storage: self.storage.clone(),
files: self.files.clone(),
crates_map: self.crates_map.clone(),
}
}
}

View file

@ -11,7 +11,7 @@ use itertools::Itertools;
use salsa::{Cancelled, Database};
use crate::{
base_db::{CrateId, RootQueryDb},
base_db::{Crate, RootQueryDb},
symbol_index::SymbolsDatabase,
FxIndexMap, RootDatabase,
};
@ -35,20 +35,22 @@ pub fn parallel_prime_caches(
) {
let _p = tracing::info_span!("parallel_prime_caches").entered();
let graph = db.crate_graph();
let mut crates_to_prime = {
// FIXME: We already have the crate list topologically sorted (but without the things
// `TopologicalSortIter` gives us). Maybe there is a way to avoid using it and rip it out
// of the codebase?
let mut builder = topologic_sort::TopologicalSortIter::builder();
for crate_id in graph.iter() {
builder.add(crate_id, graph[crate_id].dependencies.iter().map(|d| d.crate_id));
for &crate_id in db.all_crates().iter() {
builder.add(crate_id, crate_id.data(db).dependencies.iter().map(|d| d.crate_id));
}
builder.build()
};
enum ParallelPrimeCacheWorkerProgress {
BeginCrate { crate_id: CrateId, crate_name: Symbol },
EndCrate { crate_id: CrateId },
BeginCrate { crate_id: Crate, crate_name: Symbol },
EndCrate { crate_id: Crate },
}
// We split off def map computation from other work,
@ -108,16 +110,14 @@ pub fn parallel_prime_caches(
while crates_done < crates_total {
db.unwind_if_revision_cancelled();
for crate_id in &mut crates_to_prime {
let krate = &graph[crate_id];
let name = krate
.display_name
.as_deref()
.cloned()
.unwrap_or_else(|| Symbol::integer(crate_id.into_raw().into_u32() as usize));
if krate.origin.is_lang() {
additional_phases.push((crate_id, name.clone(), PrimingPhase::ImportMap));
} else if krate.origin.is_local() {
for krate in &mut crates_to_prime {
let name = krate.extra_data(db).display_name.as_deref().cloned().unwrap_or_else(|| {
Symbol::integer(salsa::plumbing::AsId::as_id(&krate).as_u32() as usize)
});
let origin = &krate.data(db).origin;
if origin.is_lang() {
additional_phases.push((krate, name.clone(), PrimingPhase::ImportMap));
} else if origin.is_local() {
// Compute the symbol search index.
// This primes the cache for `ide_db::symbol_index::world_symbols()`.
//
@ -127,10 +127,10 @@ pub fn parallel_prime_caches(
// FIXME: We should do it unconditionally if the configuration is set to default to
// searching dependencies (rust-analyzer.workspace.symbol.search.scope), but we
// would need to pipe that configuration information down here.
additional_phases.push((crate_id, name.clone(), PrimingPhase::CrateSymbols));
additional_phases.push((krate, name.clone(), PrimingPhase::CrateSymbols));
}
work_sender.send((crate_id, name, PrimingPhase::DefMap)).ok();
work_sender.send((krate, name, PrimingPhase::DefMap)).ok();
}
// recv_timeout is somewhat a hack, we need a way to from this thread check to see if the current salsa revision

View file

@ -162,13 +162,13 @@ impl SearchScope {
fn crate_graph(db: &RootDatabase) -> SearchScope {
let mut entries = FxHashMap::default();
let graph = db.crate_graph();
for krate in graph.iter() {
let root_file = graph[krate].root_file_id;
let source_root = db.file_source_root(root_file).source_root_id(db);
let all_crates = db.all_crates();
for &krate in all_crates.iter() {
let crate_data = krate.data(db);
let source_root = db.file_source_root(crate_data.root_file_id).source_root_id(db);
let source_root = db.source_root(source_root).source_root(db);
entries.extend(
source_root.iter().map(|id| (EditionedFileId::new(id, graph[krate].edition), None)),
source_root.iter().map(|id| (EditionedFileId::new(id, crate_data.edition), None)),
);
}
SearchScope { entries }

View file

@ -2,7 +2,9 @@
(
Module {
id: ModuleId {
krate: Idx::<CrateData>(0),
krate: Crate {
[salsa id]: Id(2c00),
},
block: None,
local_id: Idx::<ModuleData>(0),
},

View file

@ -2,7 +2,9 @@
(
Module {
id: ModuleId {
krate: Idx::<CrateData>(0),
krate: Crate {
[salsa id]: Id(2c00),
},
block: None,
local_id: Idx::<ModuleData>(0),
},
@ -532,7 +534,9 @@
def: Module(
Module {
id: ModuleId {
krate: Idx::<CrateData>(0),
krate: Crate {
[salsa id]: Id(2c00),
},
block: None,
local_id: Idx::<ModuleData>(1),
},
@ -565,7 +569,9 @@
def: Module(
Module {
id: ModuleId {
krate: Idx::<CrateData>(0),
krate: Crate {
[salsa id]: Id(2c00),
},
block: None,
local_id: Idx::<ModuleData>(2),
},
@ -827,7 +833,9 @@
(
Module {
id: ModuleId {
krate: Idx::<CrateData>(0),
krate: Crate {
[salsa id]: Id(2c00),
},
block: None,
local_id: Idx::<ModuleData>(1),
},
@ -871,7 +879,9 @@
(
Module {
id: ModuleId {
krate: Idx::<CrateData>(0),
krate: Crate {
[salsa id]: Id(2c00),
},
block: None,
local_id: Idx::<ModuleData>(2),
},