diff options
Diffstat (limited to 'crates/ide_db/src/change.rs')
-rw-r--r-- | crates/ide_db/src/change.rs | 318 |
1 files changed, 318 insertions, 0 deletions
diff --git a/crates/ide_db/src/change.rs b/crates/ide_db/src/change.rs new file mode 100644 index 000000000..8b4fd7ab8 --- /dev/null +++ b/crates/ide_db/src/change.rs | |||
@@ -0,0 +1,318 @@ | |||
1 | //! Defines a unit of change that can applied to a state of IDE to get the next | ||
2 | //! state. Changes are transactional. | ||
3 | |||
4 | use std::{fmt, sync::Arc, time}; | ||
5 | |||
6 | use base_db::{ | ||
7 | salsa::{Database, Durability, SweepStrategy}, | ||
8 | CrateGraph, FileId, SourceDatabase, SourceDatabaseExt, SourceRoot, SourceRootId, | ||
9 | }; | ||
10 | use profile::{memory_usage, Bytes}; | ||
11 | use rustc_hash::FxHashSet; | ||
12 | |||
13 | use crate::{symbol_index::SymbolsDatabase, RootDatabase}; | ||
14 | |||
15 | #[derive(Default)] | ||
16 | pub struct AnalysisChange { | ||
17 | roots: Option<Vec<SourceRoot>>, | ||
18 | files_changed: Vec<(FileId, Option<Arc<String>>)>, | ||
19 | crate_graph: Option<CrateGraph>, | ||
20 | } | ||
21 | |||
22 | impl fmt::Debug for AnalysisChange { | ||
23 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { | ||
24 | let mut d = fmt.debug_struct("AnalysisChange"); | ||
25 | if let Some(roots) = &self.roots { | ||
26 | d.field("roots", roots); | ||
27 | } | ||
28 | if !self.files_changed.is_empty() { | ||
29 | d.field("files_changed", &self.files_changed.len()); | ||
30 | } | ||
31 | if self.crate_graph.is_some() { | ||
32 | d.field("crate_graph", &self.crate_graph); | ||
33 | } | ||
34 | d.finish() | ||
35 | } | ||
36 | } | ||
37 | |||
38 | impl AnalysisChange { | ||
39 | pub fn new() -> AnalysisChange { | ||
40 | AnalysisChange::default() | ||
41 | } | ||
42 | |||
43 | pub fn set_roots(&mut self, roots: Vec<SourceRoot>) { | ||
44 | self.roots = Some(roots); | ||
45 | } | ||
46 | |||
47 | pub fn change_file(&mut self, file_id: FileId, new_text: Option<Arc<String>>) { | ||
48 | self.files_changed.push((file_id, new_text)) | ||
49 | } | ||
50 | |||
51 | pub fn set_crate_graph(&mut self, graph: CrateGraph) { | ||
52 | self.crate_graph = Some(graph); | ||
53 | } | ||
54 | } | ||
55 | |||
56 | #[derive(Debug)] | ||
57 | struct AddFile { | ||
58 | file_id: FileId, | ||
59 | path: String, | ||
60 | text: Arc<String>, | ||
61 | } | ||
62 | |||
63 | #[derive(Debug)] | ||
64 | struct RemoveFile { | ||
65 | file_id: FileId, | ||
66 | path: String, | ||
67 | } | ||
68 | |||
69 | #[derive(Default)] | ||
70 | struct RootChange { | ||
71 | added: Vec<AddFile>, | ||
72 | removed: Vec<RemoveFile>, | ||
73 | } | ||
74 | |||
75 | impl fmt::Debug for RootChange { | ||
76 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { | ||
77 | fmt.debug_struct("AnalysisChange") | ||
78 | .field("added", &self.added.len()) | ||
79 | .field("removed", &self.removed.len()) | ||
80 | .finish() | ||
81 | } | ||
82 | } | ||
83 | |||
84 | const GC_COOLDOWN: time::Duration = time::Duration::from_millis(100); | ||
85 | |||
86 | impl RootDatabase { | ||
87 | pub fn request_cancellation(&mut self) { | ||
88 | let _p = profile::span("RootDatabase::request_cancellation"); | ||
89 | self.salsa_runtime_mut().synthetic_write(Durability::LOW); | ||
90 | } | ||
91 | |||
92 | pub fn apply_change(&mut self, change: AnalysisChange) { | ||
93 | let _p = profile::span("RootDatabase::apply_change"); | ||
94 | self.request_cancellation(); | ||
95 | log::info!("apply_change {:?}", change); | ||
96 | if let Some(roots) = change.roots { | ||
97 | let mut local_roots = FxHashSet::default(); | ||
98 | let mut library_roots = FxHashSet::default(); | ||
99 | for (idx, root) in roots.into_iter().enumerate() { | ||
100 | let root_id = SourceRootId(idx as u32); | ||
101 | let durability = durability(&root); | ||
102 | if root.is_library { | ||
103 | library_roots.insert(root_id); | ||
104 | } else { | ||
105 | local_roots.insert(root_id); | ||
106 | } | ||
107 | for file_id in root.iter() { | ||
108 | self.set_file_source_root_with_durability(file_id, root_id, durability); | ||
109 | } | ||
110 | self.set_source_root_with_durability(root_id, Arc::new(root), durability); | ||
111 | } | ||
112 | self.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH); | ||
113 | self.set_library_roots_with_durability(Arc::new(library_roots), Durability::HIGH); | ||
114 | } | ||
115 | |||
116 | for (file_id, text) in change.files_changed { | ||
117 | let source_root_id = self.file_source_root(file_id); | ||
118 | let source_root = self.source_root(source_root_id); | ||
119 | let durability = durability(&source_root); | ||
120 | // XXX: can't actually remove the file, just reset the text | ||
121 | let text = text.unwrap_or_default(); | ||
122 | self.set_file_text_with_durability(file_id, text, durability) | ||
123 | } | ||
124 | if let Some(crate_graph) = change.crate_graph { | ||
125 | self.set_crate_graph_with_durability(Arc::new(crate_graph), Durability::HIGH) | ||
126 | } | ||
127 | } | ||
128 | |||
129 | pub fn maybe_collect_garbage(&mut self) { | ||
130 | if cfg!(feature = "wasm") { | ||
131 | return; | ||
132 | } | ||
133 | |||
134 | if self.last_gc_check.elapsed() > GC_COOLDOWN { | ||
135 | self.last_gc_check = crate::wasm_shims::Instant::now(); | ||
136 | } | ||
137 | } | ||
138 | |||
139 | pub fn collect_garbage(&mut self) { | ||
140 | if cfg!(feature = "wasm") { | ||
141 | return; | ||
142 | } | ||
143 | |||
144 | let _p = profile::span("RootDatabase::collect_garbage"); | ||
145 | self.last_gc = crate::wasm_shims::Instant::now(); | ||
146 | |||
147 | let sweep = SweepStrategy::default().discard_values().sweep_all_revisions(); | ||
148 | |||
149 | base_db::ParseQuery.in_db(self).sweep(sweep); | ||
150 | hir::db::ParseMacroQuery.in_db(self).sweep(sweep); | ||
151 | |||
152 | // Macros do take significant space, but less then the syntax trees | ||
153 | // self.query(hir::db::MacroDefQuery).sweep(sweep); | ||
154 | // self.query(hir::db::MacroArgTextQuery).sweep(sweep); | ||
155 | // self.query(hir::db::MacroExpandQuery).sweep(sweep); | ||
156 | |||
157 | hir::db::AstIdMapQuery.in_db(self).sweep(sweep); | ||
158 | |||
159 | hir::db::BodyWithSourceMapQuery.in_db(self).sweep(sweep); | ||
160 | |||
161 | hir::db::ExprScopesQuery.in_db(self).sweep(sweep); | ||
162 | hir::db::InferQueryQuery.in_db(self).sweep(sweep); | ||
163 | hir::db::BodyQuery.in_db(self).sweep(sweep); | ||
164 | } | ||
165 | |||
166 | // Feature: Memory Usage | ||
167 | // | ||
168 | // Clears rust-analyzer's internal database and prints memory usage statistics. | ||
169 | // | ||
170 | // |=== | ||
171 | // | Editor | Action Name | ||
172 | // | ||
173 | // | VS Code | **Rust Analyzer: Memory Usage (Clears Database)** | ||
174 | // |=== | ||
175 | pub fn per_query_memory_usage(&mut self) -> Vec<(String, Bytes)> { | ||
176 | let mut acc: Vec<(String, Bytes)> = vec![]; | ||
177 | let sweep = SweepStrategy::default().discard_values().sweep_all_revisions(); | ||
178 | macro_rules! sweep_each_query { | ||
179 | ($($q:path)*) => {$( | ||
180 | let before = memory_usage().allocated; | ||
181 | $q.in_db(self).sweep(sweep); | ||
182 | let after = memory_usage().allocated; | ||
183 | let q: $q = Default::default(); | ||
184 | let name = format!("{:?}", q); | ||
185 | acc.push((name, before - after)); | ||
186 | |||
187 | let before = memory_usage().allocated; | ||
188 | $q.in_db(self).sweep(sweep.discard_everything()); | ||
189 | let after = memory_usage().allocated; | ||
190 | let q: $q = Default::default(); | ||
191 | let name = format!("{:?} (deps)", q); | ||
192 | acc.push((name, before - after)); | ||
193 | |||
194 | let before = memory_usage().allocated; | ||
195 | $q.in_db(self).purge(); | ||
196 | let after = memory_usage().allocated; | ||
197 | let q: $q = Default::default(); | ||
198 | let name = format!("{:?} (purge)", q); | ||
199 | acc.push((name, before - after)); | ||
200 | )*} | ||
201 | } | ||
202 | sweep_each_query![ | ||
203 | // SourceDatabase | ||
204 | base_db::ParseQuery | ||
205 | base_db::CrateGraphQuery | ||
206 | |||
207 | // SourceDatabaseExt | ||
208 | base_db::FileTextQuery | ||
209 | base_db::FileSourceRootQuery | ||
210 | base_db::SourceRootQuery | ||
211 | base_db::SourceRootCratesQuery | ||
212 | |||
213 | // AstDatabase | ||
214 | hir::db::AstIdMapQuery | ||
215 | hir::db::MacroArgTextQuery | ||
216 | hir::db::MacroDefQuery | ||
217 | hir::db::ParseMacroQuery | ||
218 | hir::db::MacroExpandQuery | ||
219 | |||
220 | // DefDatabase | ||
221 | hir::db::ItemTreeQuery | ||
222 | hir::db::CrateDefMapQueryQuery | ||
223 | hir::db::StructDataQuery | ||
224 | hir::db::UnionDataQuery | ||
225 | hir::db::EnumDataQuery | ||
226 | hir::db::ImplDataQuery | ||
227 | hir::db::TraitDataQuery | ||
228 | hir::db::TypeAliasDataQuery | ||
229 | hir::db::FunctionDataQuery | ||
230 | hir::db::ConstDataQuery | ||
231 | hir::db::StaticDataQuery | ||
232 | hir::db::BodyWithSourceMapQuery | ||
233 | hir::db::BodyQuery | ||
234 | hir::db::ExprScopesQuery | ||
235 | hir::db::GenericParamsQuery | ||
236 | hir::db::AttrsQuery | ||
237 | hir::db::ModuleLangItemsQuery | ||
238 | hir::db::CrateLangItemsQuery | ||
239 | hir::db::LangItemQuery | ||
240 | hir::db::DocumentationQuery | ||
241 | hir::db::ImportMapQuery | ||
242 | |||
243 | // HirDatabase | ||
244 | hir::db::InferQueryQuery | ||
245 | hir::db::TyQuery | ||
246 | hir::db::ValueTyQuery | ||
247 | hir::db::ImplSelfTyQuery | ||
248 | hir::db::ImplTraitQuery | ||
249 | hir::db::FieldTypesQuery | ||
250 | hir::db::CallableItemSignatureQuery | ||
251 | hir::db::GenericPredicatesForParamQuery | ||
252 | hir::db::GenericPredicatesQuery | ||
253 | hir::db::GenericDefaultsQuery | ||
254 | hir::db::InherentImplsInCrateQuery | ||
255 | hir::db::TraitImplsInCrateQuery | ||
256 | hir::db::TraitImplsInDepsQuery | ||
257 | hir::db::AssociatedTyDataQuery | ||
258 | hir::db::AssociatedTyDataQuery | ||
259 | hir::db::TraitDatumQuery | ||
260 | hir::db::StructDatumQuery | ||
261 | hir::db::ImplDatumQuery | ||
262 | hir::db::FnDefDatumQuery | ||
263 | hir::db::ReturnTypeImplTraitsQuery | ||
264 | hir::db::InternCallableDefQuery | ||
265 | hir::db::InternTypeParamIdQuery | ||
266 | hir::db::InternImplTraitIdQuery | ||
267 | hir::db::InternClosureQuery | ||
268 | hir::db::AssociatedTyValueQuery | ||
269 | hir::db::TraitSolveQuery | ||
270 | |||
271 | // SymbolsDatabase | ||
272 | crate::symbol_index::FileSymbolsQuery | ||
273 | crate::symbol_index::LibrarySymbolsQuery | ||
274 | crate::symbol_index::LocalRootsQuery | ||
275 | crate::symbol_index::LibraryRootsQuery | ||
276 | |||
277 | // LineIndexDatabase | ||
278 | crate::LineIndexQuery | ||
279 | ]; | ||
280 | |||
281 | // To collect interned data, we need to bump the revision counter by performing a synthetic | ||
282 | // write. | ||
283 | // We do this after collecting the non-interned queries to correctly attribute memory used | ||
284 | // by interned data. | ||
285 | self.salsa_runtime_mut().synthetic_write(Durability::HIGH); | ||
286 | |||
287 | sweep_each_query![ | ||
288 | // AstDatabase | ||
289 | hir::db::InternMacroQuery | ||
290 | hir::db::InternEagerExpansionQuery | ||
291 | |||
292 | // InternDatabase | ||
293 | hir::db::InternFunctionQuery | ||
294 | hir::db::InternStructQuery | ||
295 | hir::db::InternUnionQuery | ||
296 | hir::db::InternEnumQuery | ||
297 | hir::db::InternConstQuery | ||
298 | hir::db::InternStaticQuery | ||
299 | hir::db::InternTraitQuery | ||
300 | hir::db::InternTypeAliasQuery | ||
301 | hir::db::InternImplQuery | ||
302 | |||
303 | // HirDatabase | ||
304 | hir::db::InternTypeParamIdQuery | ||
305 | ]; | ||
306 | |||
307 | acc.sort_by_key(|it| std::cmp::Reverse(it.1)); | ||
308 | acc | ||
309 | } | ||
310 | } | ||
311 | |||
312 | fn durability(source_root: &SourceRoot) -> Durability { | ||
313 | if source_root.is_library { | ||
314 | Durability::HIGH | ||
315 | } else { | ||
316 | Durability::LOW | ||
317 | } | ||
318 | } | ||