Unnamed repository; edit this file 'description' to name the repository.
| -rw-r--r-- | Cargo.lock | 1 | ||||
| -rw-r--r-- | crates/hir-ty/src/next_solver/consts.rs | 24 | ||||
| -rw-r--r-- | crates/hir-ty/src/next_solver/interner.rs | 132 | ||||
| -rw-r--r-- | crates/hir-ty/src/next_solver/opaques.rs | 5 | ||||
| -rw-r--r-- | crates/hir-ty/src/next_solver/predicate.rs | 31 | ||||
| -rw-r--r-- | crates/hir-ty/src/next_solver/region.rs | 8 | ||||
| -rw-r--r-- | crates/hir-ty/src/next_solver/ty.rs | 8 | ||||
| -rw-r--r-- | crates/hir/src/lib.rs | 2 | ||||
| -rw-r--r-- | crates/intern/Cargo.toml | 1 | ||||
| -rw-r--r-- | crates/intern/src/gc.rs | 325 | ||||
| -rw-r--r-- | crates/intern/src/intern.rs | 11 | ||||
| -rw-r--r-- | crates/intern/src/intern_slice.rs | 17 | ||||
| -rw-r--r-- | crates/intern/src/lib.rs | 2 | ||||
| -rw-r--r-- | crates/rust-analyzer/src/cli/analysis_stats.rs | 1 | ||||
| -rw-r--r-- | crates/rust-analyzer/src/config.rs | 13 | ||||
| -rw-r--r-- | crates/rust-analyzer/src/global_state.rs | 13 | ||||
| -rw-r--r-- | docs/book/src/configuration_generated.md | 11 | ||||
| -rw-r--r-- | editors/code/package.json | 11 |
18 files changed, 566 insertions, 50 deletions
diff --git a/Cargo.lock b/Cargo.lock index 081c7d66f1..7ada91d2b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1214,6 +1214,7 @@ version = "0.0.0" dependencies = [ "dashmap", "hashbrown 0.14.5", + "rayon", "rustc-hash 2.1.1", "smallvec", "triomphe", diff --git a/crates/hir-ty/src/next_solver/consts.rs b/crates/hir-ty/src/next_solver/consts.rs index 8417e145a4..c2fc4d18bb 100644 --- a/crates/hir-ty/src/next_solver/consts.rs +++ b/crates/hir-ty/src/next_solver/consts.rs @@ -29,9 +29,9 @@ pub struct Const<'db> { pub(super) interned: InternedRef<'db, ConstInterned>, } -#[derive(PartialEq, Eq, Hash)] +#[derive(PartialEq, Eq, Hash, GenericTypeVisitable)] #[repr(align(4))] // Required for `GenericArg` bit-tagging. -pub(super) struct ConstInterned(WithCachedTypeInfo<ConstKind<'static>>); +pub(super) struct ConstInterned(pub(super) WithCachedTypeInfo<ConstKind<'static>>); impl_internable!(gc; ConstInterned); impl_stored_interned!(ConstInterned, Const, StoredConst); @@ -219,15 +219,14 @@ pub struct Valtree<'db> { impl<'db, V: super::WorldExposer> GenericTypeVisitable<V> for Valtree<'db> { fn generic_visit_with(&self, visitor: &mut V) { - visitor.on_interned(self.interned); - self.inner().generic_visit_with(visitor); + if visitor.on_interned(self.interned).is_continue() { + self.inner().generic_visit_with(visitor); + } } } -#[derive(Debug, PartialEq, Eq, Hash)] -struct ValtreeInterned { - bytes: ConstBytes<'static>, -} +#[derive(Debug, PartialEq, Eq, Hash, GenericTypeVisitable)] +pub(super) struct ValtreeInterned(ConstBytes<'static>); impl_internable!(gc; ValtreeInterned); @@ -240,12 +239,12 @@ impl<'db> Valtree<'db> { #[inline] pub fn new(bytes: ConstBytes<'db>) -> Self { let bytes = unsafe { std::mem::transmute::<ConstBytes<'db>, ConstBytes<'static>>(bytes) }; - Self { interned: Interned::new_gc(ValtreeInterned { bytes }) } + Self { interned: Interned::new_gc(ValtreeInterned(bytes)) } } #[inline] pub fn inner(&self) -> &ConstBytes<'db> { - let inner = &self.interned.bytes; + let inner = &self.interned.0; unsafe { std::mem::transmute::<&ConstBytes<'static>, &ConstBytes<'db>>(inner) } } } @@ -277,8 +276,9 @@ impl<'db> IntoKind for Const<'db> { impl<'db, V: super::WorldExposer> GenericTypeVisitable<V> for Const<'db> { fn generic_visit_with(&self, visitor: &mut V) { - visitor.on_interned(self.interned); - self.kind().generic_visit_with(visitor); + if visitor.on_interned(self.interned).is_continue() { + self.kind().generic_visit_with(visitor); + } } } diff --git a/crates/hir-ty/src/next_solver/interner.rs b/crates/hir-ty/src/next_solver/interner.rs index 1a946cb908..f2dacd16db 100644 --- a/crates/hir-ty/src/next_solver/interner.rs +++ b/crates/hir-ty/src/next_solver/interner.rs @@ -1,8 +1,9 @@ //! Things related to the Interner in the next-trait-solver. -use std::fmt; +use std::{fmt, ops::ControlFlow}; use intern::{Interned, InternedRef, InternedSliceRef, impl_internable}; +use macros::GenericTypeVisitable; use rustc_ast_ir::{FloatTy, IntTy, UintTy}; pub use tls_cache::clear_tls_solver_cache; pub use tls_db::{attach_db, attach_db_allow_change, with_attached_db}; @@ -21,8 +22,8 @@ use rustc_hash::FxHashSet; use rustc_index::bit_set::DenseBitSet; use rustc_type_ir::{ AliasTermKind, AliasTyKind, BoundVar, CollectAndApply, CoroutineWitnessTypes, DebruijnIndex, - EarlyBinder, FlagComputation, Flags, GenericArgKind, ImplPolarity, InferTy, Interner, TraitRef, - TypeFlags, TypeVisitableExt, UniverseIndex, Upcast, Variance, + EarlyBinder, FlagComputation, Flags, GenericArgKind, GenericTypeVisitable, ImplPolarity, + InferTy, Interner, TraitRef, TypeFlags, TypeVisitableExt, UniverseIndex, Upcast, Variance, elaborate::elaborate, error::TypeError, fast_reject, @@ -169,8 +170,9 @@ macro_rules! interned_slice { { #[inline] fn generic_visit_with(&self, visitor: &mut V) { - visitor.on_interned_slice(self.interned); - self.as_slice().iter().for_each(|it| it.generic_visit_with(visitor)); + if visitor.on_interned_slice(self.interned).is_continue() { + self.as_slice().iter().for_each(|it| it.generic_visit_with(visitor)); + } } } }; @@ -293,8 +295,14 @@ macro_rules! impl_stored_interned { pub(crate) use impl_stored_interned; pub trait WorldExposer { - fn on_interned<T: intern::Internable>(&mut self, interned: InternedRef<'_, T>); - fn on_interned_slice<T: intern::SliceInternable>(&mut self, interned: InternedSliceRef<'_, T>); + fn on_interned<T: intern::Internable>( + &mut self, + interned: InternedRef<'_, T>, + ) -> ControlFlow<()>; + fn on_interned_slice<T: intern::SliceInternable>( + &mut self, + interned: InternedSliceRef<'_, T>, + ) -> ControlFlow<()>; } #[derive(Debug, Copy, Clone)] @@ -803,7 +811,7 @@ pub struct Pattern<'db> { interned: InternedRef<'db, PatternInterned>, } -#[derive(PartialEq, Eq, Hash)] +#[derive(PartialEq, Eq, Hash, GenericTypeVisitable)] struct PatternInterned(PatternKind<'static>); impl_internable!(gc; PatternInterned); @@ -884,8 +892,9 @@ impl<'db> rustc_type_ir::TypeVisitable<DbInterner<'db>> for Pattern<'db> { impl<'db, V: WorldExposer> rustc_type_ir::GenericTypeVisitable<V> for Pattern<'db> { fn generic_visit_with(&self, visitor: &mut V) { - visitor.on_interned(self.interned); - self.kind().generic_visit_with(visitor); + if visitor.on_interned(self.interned).is_continue() { + self.kind().generic_visit_with(visitor); + } } } @@ -2559,3 +2568,106 @@ mod tls_cache { GLOBAL_CACHE.with_borrow_mut(|handle| *handle = None); } } + +impl WorldExposer for intern::GarbageCollector { + fn on_interned<T: intern::Internable>( + &mut self, + interned: InternedRef<'_, T>, + ) -> ControlFlow<()> { + self.mark_interned_alive(interned) + } + + fn on_interned_slice<T: intern::SliceInternable>( + &mut self, + interned: InternedSliceRef<'_, T>, + ) -> ControlFlow<()> { + self.mark_interned_slice_alive(interned) + } +} + +/// # Safety +/// +/// This cannot be called if there are some not-yet-recorded type values. Generally, if you have a mutable +/// reference to the database, and there are no other database - then you can call this safely, but you +/// also need to make sure to maintain the mutable reference while this is running. +pub unsafe fn collect_ty_garbage() { + let mut gc = intern::GarbageCollector::default(); + + gc.add_storage::<super::consts::ConstInterned>(); + gc.add_storage::<super::consts::ValtreeInterned>(); + gc.add_storage::<PatternInterned>(); + gc.add_storage::<super::opaques::ExternalConstraintsInterned>(); + gc.add_storage::<super::predicate::PredicateInterned>(); + gc.add_storage::<super::region::RegionInterned>(); + gc.add_storage::<super::ty::TyInterned>(); + + gc.add_slice_storage::<super::predicate::ClausesStorage>(); + gc.add_slice_storage::<super::generic_arg::GenericArgsStorage>(); + gc.add_slice_storage::<BoundVarKindsStorage>(); + gc.add_slice_storage::<VariancesOfStorage>(); + gc.add_slice_storage::<CanonicalVarsStorage>(); + gc.add_slice_storage::<PatListStorage>(); + gc.add_slice_storage::<super::opaques::PredefinedOpaquesStorage>(); + gc.add_slice_storage::<super::opaques::SolverDefIdsStorage>(); + gc.add_slice_storage::<super::predicate::BoundExistentialPredicatesStorage>(); + gc.add_slice_storage::<super::region::RegionAssumptionsStorage>(); + gc.add_slice_storage::<super::ty::TysStorage>(); + + unsafe { gc.collect() }; +} + +macro_rules! impl_gc_visit { + ( $($ty:ty),* $(,)? ) => { + $( + impl ::intern::GcInternedVisit for $ty { + #[inline] + fn visit_with(&self, gc: &mut ::intern::GarbageCollector) { + self.generic_visit_with(gc); + } + } + )* + }; +} + +impl_gc_visit!( + super::consts::ConstInterned, + super::consts::ValtreeInterned, + PatternInterned, + super::opaques::ExternalConstraintsInterned, + super::predicate::PredicateInterned, + super::region::RegionInterned, + super::ty::TyInterned, + super::predicate::ClausesCachedTypeInfo, +); + +macro_rules! impl_gc_visit_slice { + ( $($ty:ty),* $(,)? ) => { + $( + impl ::intern::GcInternedSliceVisit for $ty { + #[inline] + fn visit_header(header: &<Self as ::intern::SliceInternable>::Header, gc: &mut ::intern::GarbageCollector) { + header.generic_visit_with(gc); + } + + #[inline] + fn visit_slice(header: &[<Self as ::intern::SliceInternable>::SliceType], gc: &mut ::intern::GarbageCollector) { + header.generic_visit_with(gc); + } + } + )* + }; +} + +impl_gc_visit_slice!( + super::predicate::ClausesStorage, + super::generic_arg::GenericArgsStorage, + BoundVarKindsStorage, + VariancesOfStorage, + CanonicalVarsStorage, + PatListStorage, + super::opaques::PredefinedOpaquesStorage, + super::opaques::SolverDefIdsStorage, + super::predicate::BoundExistentialPredicatesStorage, + super::region::RegionAssumptionsStorage, + super::ty::TysStorage, +); diff --git a/crates/hir-ty/src/next_solver/opaques.rs b/crates/hir-ty/src/next_solver/opaques.rs index 7e1d6b7328..230469c21a 100644 --- a/crates/hir-ty/src/next_solver/opaques.rs +++ b/crates/hir-ty/src/next_solver/opaques.rs @@ -1,6 +1,7 @@ //! Things related to opaques in the next-trait-solver. use intern::{Interned, InternedRef, impl_internable}; +use macros::GenericTypeVisitable; use rustc_ast_ir::try_visit; use rustc_type_ir::inherent::SliceLike; @@ -30,8 +31,8 @@ pub struct ExternalConstraints<'db> { interned: InternedRef<'db, ExternalConstraintsInterned>, } -#[derive(PartialEq, Eq, Hash)] -struct ExternalConstraintsInterned(ExternalConstraintsData<'static>); +#[derive(PartialEq, Eq, Hash, GenericTypeVisitable)] +pub(super) struct ExternalConstraintsInterned(ExternalConstraintsData<'static>); impl_internable!(gc; ExternalConstraintsInterned); diff --git a/crates/hir-ty/src/next_solver/predicate.rs b/crates/hir-ty/src/next_solver/predicate.rs index 95b48c73c1..6d7539575f 100644 --- a/crates/hir-ty/src/next_solver/predicate.rs +++ b/crates/hir-ty/src/next_solver/predicate.rs @@ -186,8 +186,8 @@ pub struct Predicate<'db> { interned: InternedRef<'db, PredicateInterned>, } -#[derive(PartialEq, Eq, Hash)] -struct PredicateInterned(WithCachedTypeInfo<Binder<'static, PredicateKind<'static>>>); +#[derive(PartialEq, Eq, Hash, GenericTypeVisitable)] +pub(super) struct PredicateInterned(WithCachedTypeInfo<Binder<'static, PredicateKind<'static>>>); impl_internable!(gc; PredicateInterned); @@ -252,7 +252,10 @@ impl<'db> std::fmt::Debug for Predicate<'db> { } } -impl_slice_internable!(gc; ClausesStorage, WithCachedTypeInfo<()>, Clause<'static>); +#[derive(Clone, Copy, PartialEq, Eq, Hash, GenericTypeVisitable)] +pub struct ClausesCachedTypeInfo(WithCachedTypeInfo<()>); + +impl_slice_internable!(gc; ClausesStorage, ClausesCachedTypeInfo, Clause<'static>); impl_stored_interned_slice!(ClausesStorage, Clauses, StoredClauses); #[derive(Clone, Copy, PartialEq, Eq, Hash)] @@ -277,11 +280,11 @@ impl<'db> Clauses<'db> { pub fn new_from_slice(slice: &[Clause<'db>]) -> Self { let slice = unsafe { ::std::mem::transmute::<&[Clause<'db>], &[Clause<'static>]>(slice) }; let flags = FlagComputation::<DbInterner<'db>>::for_clauses(slice); - let flags = WithCachedTypeInfo { + let flags = ClausesCachedTypeInfo(WithCachedTypeInfo { internee: (), flags: flags.flags, outer_exclusive_binder: flags.outer_exclusive_binder, - }; + }); Self { interned: InternedSlice::from_header_and_slice(flags, slice) } } @@ -400,20 +403,21 @@ impl<'db> rustc_type_ir::TypeVisitable<DbInterner<'db>> for Clauses<'db> { impl<'db, V: super::WorldExposer> rustc_type_ir::GenericTypeVisitable<V> for Clauses<'db> { fn generic_visit_with(&self, visitor: &mut V) { - visitor.on_interned_slice(self.interned); - self.as_slice().iter().for_each(|it| it.generic_visit_with(visitor)); + if visitor.on_interned_slice(self.interned).is_continue() { + self.as_slice().iter().for_each(|it| it.generic_visit_with(visitor)); + } } } impl<'db> rustc_type_ir::Flags for Clauses<'db> { #[inline] fn flags(&self) -> rustc_type_ir::TypeFlags { - self.interned.header.header.flags + self.interned.header.header.0.flags } #[inline] fn outer_exclusive_binder(&self) -> rustc_type_ir::DebruijnIndex { - self.interned.header.header.outer_exclusive_binder + self.interned.header.header.0.outer_exclusive_binder } } @@ -430,7 +434,9 @@ impl<'db> rustc_type_ir::TypeSuperVisitable<DbInterner<'db>> for Clauses<'db> { pub struct Clause<'db>(pub(crate) Predicate<'db>); // We could cram the reveal into the clauses like rustc does, probably -#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, TypeVisitable, TypeFoldable)] +#[derive( + Copy, Clone, Debug, Hash, PartialEq, Eq, TypeVisitable, TypeFoldable, GenericTypeVisitable, +)] pub struct ParamEnv<'db> { pub(crate) clauses: Clauses<'db>, } @@ -474,8 +480,9 @@ impl<'db> TypeVisitable<DbInterner<'db>> for Predicate<'db> { impl<'db, V: super::WorldExposer> GenericTypeVisitable<V> for Predicate<'db> { fn generic_visit_with(&self, visitor: &mut V) { - visitor.on_interned(self.interned); - self.kind().generic_visit_with(visitor); + if visitor.on_interned(self.interned).is_continue() { + self.kind().generic_visit_with(visitor); + } } } diff --git a/crates/hir-ty/src/next_solver/region.rs b/crates/hir-ty/src/next_solver/region.rs index dee7953ae3..e34e87601f 100644 --- a/crates/hir-ty/src/next_solver/region.rs +++ b/crates/hir-ty/src/next_solver/region.rs @@ -2,6 +2,7 @@ use hir_def::LifetimeParamId; use intern::{Interned, InternedRef, Symbol, impl_internable}; +use macros::GenericTypeVisitable; use rustc_type_ir::{ BoundVar, BoundVarIndexKind, DebruijnIndex, Flags, GenericTypeVisitable, INNERMOST, RegionVid, TypeFlags, TypeFoldable, TypeVisitable, @@ -25,7 +26,7 @@ pub struct Region<'db> { pub(super) interned: InternedRef<'db, RegionInterned>, } -#[derive(PartialEq, Eq, Hash)] +#[derive(PartialEq, Eq, Hash, GenericTypeVisitable)] #[repr(align(4))] // Required for `GenericArg` bit-tagging. pub(super) struct RegionInterned(RegionKind<'static>); @@ -388,8 +389,9 @@ impl<'db> PlaceholderLike<DbInterner<'db>> for PlaceholderRegion { impl<'db, V: super::WorldExposer> GenericTypeVisitable<V> for Region<'db> { fn generic_visit_with(&self, visitor: &mut V) { - visitor.on_interned(self.interned); - self.kind().generic_visit_with(visitor); + if visitor.on_interned(self.interned).is_continue() { + self.kind().generic_visit_with(visitor); + } } } diff --git a/crates/hir-ty/src/next_solver/ty.rs b/crates/hir-ty/src/next_solver/ty.rs index 305da50b6d..85534e42a8 100644 --- a/crates/hir-ty/src/next_solver/ty.rs +++ b/crates/hir-ty/src/next_solver/ty.rs @@ -8,6 +8,7 @@ use hir_def::{ }; use hir_def::{TraitId, type_ref::Rawness}; use intern::{Interned, InternedRef, impl_internable}; +use macros::GenericTypeVisitable; use rustc_abi::{Float, Integer, Size}; use rustc_ast_ir::{Mutability, try_visit, visit::VisitorResult}; use rustc_type_ir::{ @@ -51,7 +52,7 @@ pub struct Ty<'db> { pub(super) interned: InternedRef<'db, TyInterned>, } -#[derive(PartialEq, Eq, Hash)] +#[derive(PartialEq, Eq, Hash, GenericTypeVisitable)] #[repr(align(4))] // Required for `GenericArg` bit-tagging. pub(super) struct TyInterned(WithCachedTypeInfo<TyKind<'static>>); @@ -750,8 +751,9 @@ impl<'db> IntoKind for Ty<'db> { impl<'db, V: super::WorldExposer> GenericTypeVisitable<V> for Ty<'db> { fn generic_visit_with(&self, visitor: &mut V) { - visitor.on_interned(self.interned); - self.kind().generic_visit_with(visitor); + if visitor.on_interned(self.interned).is_continue() { + self.kind().generic_visit_with(visitor); + } } } diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 3532220b72..933dd6af1d 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -175,7 +175,7 @@ pub use { layout::LayoutError, mir::{MirEvalError, MirLowerError}, next_solver::abi::Safety, - next_solver::clear_tls_solver_cache, + next_solver::{clear_tls_solver_cache, collect_ty_garbage}, }, // FIXME: These are needed for import assets, properly encapsulate them. hir_ty::{method_resolution::TraitImpls, next_solver::SimplifiedType}, diff --git a/crates/intern/Cargo.toml b/crates/intern/Cargo.toml index 44621031ab..6414f09178 100644 --- a/crates/intern/Cargo.toml +++ b/crates/intern/Cargo.toml @@ -19,6 +19,7 @@ hashbrown.workspace = true rustc-hash.workspace = true triomphe.workspace = true smallvec.workspace = true +rayon.workspace = true [lints] workspace = true diff --git a/crates/intern/src/gc.rs b/crates/intern/src/gc.rs new file mode 100644 index 0000000000..e559d1dc57 --- /dev/null +++ b/crates/intern/src/gc.rs @@ -0,0 +1,325 @@ +//! Garbage collection of interned values. + +use std::{marker::PhantomData, ops::ControlFlow}; + +use hashbrown::raw::RawTable; +use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; +use rustc_hash::{FxBuildHasher, FxHashSet}; +use triomphe::{Arc, ThinArc}; + +use crate::{Internable, InternedRef, InternedSliceRef, SliceInternable}; + +trait Storage { + fn len(&self) -> usize; + + fn mark(&self, gc: &mut GarbageCollector); + + fn sweep(&self, gc: &GarbageCollector); +} + +struct InternedStorage<T>(PhantomData<fn() -> T>); + +impl<T: Internable + GcInternedVisit> Storage for InternedStorage<T> { + fn len(&self) -> usize { + T::storage().get().len() + } + + fn mark(&self, gc: &mut GarbageCollector) { + let storage = T::storage().get(); + for item in storage { + let item = item.key(); + let addr = Arc::as_ptr(item).addr(); + if Arc::strong_count(item) > 1 { + // The item is referenced from the outside. + gc.alive.insert(addr); + item.visit_with(gc); + } + } + } + + fn sweep(&self, gc: &GarbageCollector) { + let storage = T::storage().get(); + if cfg!(miri) { + storage.shards().iter().for_each(|shard| { + gc.retain_only_alive(&mut *shard.write(), |item| item.0.as_ptr().addr()) + }); + } else { + storage.shards().par_iter().for_each(|shard| { + gc.retain_only_alive(&mut *shard.write(), |item| item.0.as_ptr().addr()) + }); + } + } +} + +struct InternedSliceStorage<T>(PhantomData<fn() -> T>); + +impl<T: SliceInternable + GcInternedSliceVisit> Storage for InternedSliceStorage<T> { + fn len(&self) -> usize { + T::storage().get().len() + } + + fn mark(&self, gc: &mut GarbageCollector) { + let storage = T::storage().get(); + for item in storage { + let item = item.key(); + let addr = ThinArc::as_ptr(item).addr(); + if ThinArc::strong_count(item) > 1 { + // The item is referenced from the outside. + gc.alive.insert(addr); + T::visit_header(&item.header.header, gc); + T::visit_slice(&item.slice, gc); + } + } + } + + fn sweep(&self, gc: &GarbageCollector) { + let storage = T::storage().get(); + if cfg!(miri) { + storage.shards().iter().for_each(|shard| { + gc.retain_only_alive(&mut *shard.write(), |item| item.0.as_ptr().addr()) + }); + } else { + storage.shards().par_iter().for_each(|shard| { + gc.retain_only_alive(&mut *shard.write(), |item| item.0.as_ptr().addr()) + }); + } + } +} + +pub trait GcInternedVisit { + fn visit_with(&self, gc: &mut GarbageCollector); +} + +pub trait GcInternedSliceVisit: SliceInternable { + fn visit_header(header: &Self::Header, gc: &mut GarbageCollector); + fn visit_slice(header: &[Self::SliceType], gc: &mut GarbageCollector); +} + +#[derive(Default)] +pub struct GarbageCollector { + alive: FxHashSet<usize>, + storages: Vec<Box<dyn Storage + Send + Sync>>, +} + +impl GarbageCollector { + pub fn add_storage<T: Internable + GcInternedVisit>(&mut self) { + const { assert!(T::USE_GC) }; + + self.storages.push(Box::new(InternedStorage::<T>(PhantomData))); + } + + pub fn add_slice_storage<T: SliceInternable + GcInternedSliceVisit>(&mut self) { + const { assert!(T::USE_GC) }; + + self.storages.push(Box::new(InternedSliceStorage::<T>(PhantomData))); + } + + /// # Safety + /// + /// This cannot be called if there are some not-yet-recorded type values. + pub unsafe fn collect(mut self) { + let total_nodes = self.storages.iter().map(|storage| storage.len()).sum(); + self.alive = FxHashSet::with_capacity_and_hasher(total_nodes, FxBuildHasher); + + let storages = std::mem::take(&mut self.storages); + + for storage in &storages { + storage.mark(&mut self); + } + + if cfg!(miri) { + storages.iter().for_each(|storage| storage.sweep(&self)); + } else { + storages.par_iter().for_each(|storage| storage.sweep(&self)); + } + } + + pub fn mark_interned_alive<T: Internable>( + &mut self, + interned: InternedRef<'_, T>, + ) -> ControlFlow<()> { + if interned.strong_count() > 1 { + // It will be visited anyway, so short-circuit + return ControlFlow::Break(()); + } + let addr = interned.as_raw().addr(); + if !self.alive.insert(addr) { ControlFlow::Break(()) } else { ControlFlow::Continue(()) } + } + + pub fn mark_interned_slice_alive<T: SliceInternable>( + &mut self, + interned: InternedSliceRef<'_, T>, + ) -> ControlFlow<()> { + if interned.strong_count() > 1 { + // It will be visited anyway, so short-circuit + return ControlFlow::Break(()); + } + let addr = interned.as_raw().addr(); + if !self.alive.insert(addr) { ControlFlow::Break(()) } else { ControlFlow::Continue(()) } + } + + #[inline] + fn retain_only_alive<T>(&self, map: &mut RawTable<T>, mut get_addr: impl FnMut(&T) -> usize) { + unsafe { + // Here we only use `iter` as a temporary, preventing use-after-free + for bucket in map.iter() { + let item = bucket.as_mut(); + let addr = get_addr(item); + if !self.alive.contains(&addr) { + map.erase(bucket); + } + } + } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + GarbageCollector, GcInternedSliceVisit, GcInternedVisit, Interned, InternedSliceRef, + }; + + crate::impl_internable!(String); + + #[test] + fn simple_interned() { + let a = Interned::new("abc".to_owned()); + let b = Interned::new("abc".to_owned()); + assert_eq!(a, b); + assert_eq!(a.as_ref(), b.as_ref()); + assert_eq!(a.as_ref(), a.as_ref()); + assert_eq!(a, a.clone()); + assert_eq!(a, a.clone().clone()); + assert_eq!(b.clone(), a.clone().clone()); + assert_eq!(*a, "abc"); + assert_eq!(*b, "abc"); + assert_eq!(b.as_ref().to_owned(), a); + let c = Interned::new("def".to_owned()); + assert_ne!(a, c); + assert_ne!(b, c); + assert_ne!(b.as_ref(), c.as_ref()); + assert_eq!(*c.as_ref(), "def"); + drop(c); + assert_eq!(*a, "abc"); + assert_eq!(*b, "abc"); + drop(a); + assert_eq!(*b, "abc"); + drop(b); + } + + #[test] + fn simple_gc() { + #[derive(Debug, PartialEq, Eq, Hash)] + struct GcString(String); + + crate::impl_internable!(gc; GcString); + + impl GcInternedVisit for GcString { + fn visit_with(&self, _gc: &mut GarbageCollector) {} + } + + crate::impl_slice_internable!(gc; StringSlice, String, String); + type InternedSlice = crate::InternedSlice<StringSlice>; + + impl GcInternedSliceVisit for StringSlice { + fn visit_header(_header: &Self::Header, _gc: &mut GarbageCollector) {} + + fn visit_slice(_header: &[Self::SliceType], _gc: &mut GarbageCollector) {} + } + + let (a, d) = { + let a = Interned::new_gc(GcString("abc".to_owned())).to_owned(); + let b = Interned::new_gc(GcString("abc".to_owned())).to_owned(); + assert_eq!(a, b); + assert_eq!(a.as_ref(), b.as_ref()); + assert_eq!(a.as_ref(), a.as_ref()); + assert_eq!(a, a.clone()); + assert_eq!(a, a.clone().clone()); + assert_eq!(b.clone(), a.clone().clone()); + assert_eq!(a.0, "abc"); + assert_eq!(b.0, "abc"); + assert_eq!(b.as_ref().to_owned(), a); + let c = Interned::new_gc(GcString("def".to_owned())).to_owned(); + assert_ne!(a, c); + assert_ne!(b, c); + assert_ne!(b.as_ref(), c.as_ref()); + assert_eq!(c.as_ref().0, "def"); + + let d = InternedSlice::from_header_and_slice( + "abc".to_owned(), + &["def".to_owned(), "123".to_owned()], + ); + let e = InternedSlice::from_header_and_slice( + "abc".to_owned(), + &["def".to_owned(), "123".to_owned()], + ); + assert_eq!(d, e); + assert_eq!(d.to_owned(), e.to_owned()); + assert_eq!(d.header.length, 2); + assert_eq!(d.header.header, "abc"); + assert_eq!(d.slice, ["def", "123"]); + (a, d.to_owned()) + }; + + let mut gc = GarbageCollector::default(); + gc.add_slice_storage::<StringSlice>(); + gc.add_storage::<GcString>(); + unsafe { gc.collect() }; + + assert_eq!(a.0, "abc"); + assert_eq!(d.header.length, 2); + assert_eq!(d.header.header, "abc"); + assert_eq!(d.slice, ["def", "123"]); + + drop(a); + drop(d); + + let mut gc = GarbageCollector::default(); + gc.add_slice_storage::<StringSlice>(); + gc.add_storage::<GcString>(); + unsafe { gc.collect() }; + } + + #[test] + fn gc_visit() { + #[derive(PartialEq, Eq, Hash)] + struct GcInterned(InternedSliceRef<'static, StringSlice>); + + crate::impl_internable!(gc; GcInterned); + + impl GcInternedVisit for GcInterned { + fn visit_with(&self, gc: &mut GarbageCollector) { + _ = gc.mark_interned_slice_alive(self.0); + } + } + + crate::impl_slice_internable!(gc; StringSlice, String, i32); + type InternedSlice = crate::InternedSlice<StringSlice>; + + impl GcInternedSliceVisit for StringSlice { + fn visit_header(_header: &Self::Header, _gc: &mut GarbageCollector) {} + + fn visit_slice(_header: &[Self::SliceType], _gc: &mut GarbageCollector) {} + } + + let outer = { + let inner = InternedSlice::from_header_and_slice("abc".to_owned(), &[123, 456, 789]); + Interned::new_gc(GcInterned(inner)).to_owned() + }; + + let mut gc = GarbageCollector::default(); + gc.add_slice_storage::<StringSlice>(); + gc.add_storage::<GcInterned>(); + unsafe { gc.collect() }; + + assert_eq!(outer.0.header.header, "abc"); + assert_eq!(outer.0.slice, [123, 456, 789]); + + drop(outer); + + let mut gc = GarbageCollector::default(); + gc.add_slice_storage::<StringSlice>(); + gc.add_storage::<GcInterned>(); + unsafe { gc.collect() }; + } +} diff --git a/crates/intern/src/intern.rs b/crates/intern/src/intern.rs index fdefb93656..5d4d001185 100644 --- a/crates/intern/src/intern.rs +++ b/crates/intern/src/intern.rs @@ -26,6 +26,8 @@ unsafe impl<T: Send + Sync + Internable> Sync for Interned<T> {} impl<T: Internable> Interned<T> { #[inline] pub fn new(obj: T) -> Self { + const { assert!(!T::USE_GC) }; + let storage = T::storage().get(); let (mut shard, hash) = Self::select(storage, &obj); // Atomically, @@ -228,6 +230,11 @@ impl<'a, T: Internable> InternedRef<'a, T> { pub unsafe fn decrement_refcount(self) { unsafe { drop(Arc::from_raw(self.as_raw())) } } + + #[inline] + pub(crate) fn strong_count(self) -> usize { + ArcBorrow::strong_count(&self.arc) + } } impl<T> Clone for InternedRef<'_, T> { @@ -292,12 +299,12 @@ impl<T: ?Sized> InternStorage<T> { } impl<T: Internable + ?Sized> InternStorage<T> { - fn get(&self) -> &InternMap<T> { + pub(crate) fn get(&self) -> &InternMap<T> { self.map.get_or_init(DashMap::default) } } -pub trait Internable: Hash + Eq + 'static { +pub trait Internable: Hash + Eq + Send + Sync + 'static { const USE_GC: bool; fn storage() -> &'static InternStorage<Self>; diff --git a/crates/intern/src/intern_slice.rs b/crates/intern/src/intern_slice.rs index 989a21e70c..7f2159ee66 100644 --- a/crates/intern/src/intern_slice.rs +++ b/crates/intern/src/intern_slice.rs @@ -256,6 +256,16 @@ impl<'a, T: SliceInternable> InternedSliceRef<'a, T> { pub unsafe fn decrement_refcount(self) { drop(ManuallyDrop::into_inner(self.arc())); } + + #[inline] + pub(crate) fn strong_count(self) -> usize { + ThinArc::strong_count(&self.arc()) + } + + #[inline] + pub(crate) fn as_raw(self) -> *const c_void { + self.arc().as_ptr() + } } impl<T> Clone for InternedSliceRef<'_, T> { @@ -318,15 +328,15 @@ impl<T: SliceInternable> InternSliceStorage<T> { } impl<T: SliceInternable> InternSliceStorage<T> { - fn get(&self) -> &InternMap<T> { + pub(crate) fn get(&self) -> &InternMap<T> { self.map.get_or_init(DashMap::default) } } pub trait SliceInternable: Sized + 'static { const USE_GC: bool; - type Header: Eq + Hash; - type SliceType: Eq + Hash + 'static; + type Header: Eq + Hash + Send + Sync; + type SliceType: Eq + Hash + Send + Sync + 'static; fn storage() -> &'static InternSliceStorage<Self>; } @@ -335,6 +345,7 @@ pub trait SliceInternable: Sized + 'static { #[doc(hidden)] macro_rules! _impl_slice_internable { ( gc; $tag:ident, $h:ty, $t:ty $(,)? ) => { + #[allow(unreachable_pub)] pub struct $tag; impl $crate::SliceInternable for $tag { const USE_GC: bool = true; diff --git a/crates/intern/src/lib.rs b/crates/intern/src/lib.rs index c5b42a860a..0c0b12427d 100644 --- a/crates/intern/src/lib.rs +++ b/crates/intern/src/lib.rs @@ -2,10 +2,12 @@ //! //! Eventually this should probably be replaced with salsa-based interning. +mod gc; mod intern; mod intern_slice; mod symbol; +pub use self::gc::{GarbageCollector, GcInternedSliceVisit, GcInternedVisit}; pub use self::intern::{InternStorage, Internable, Interned, InternedRef, impl_internable}; pub use self::intern_slice::{ InternSliceStorage, InternedSlice, InternedSliceRef, SliceInternable, impl_slice_internable, diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs index f863a7ee8d..76256b0a22 100644 --- a/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -355,6 +355,7 @@ impl flags::AnalysisStats { } hir::clear_tls_solver_cache(); + unsafe { hir::collect_ty_garbage() }; let db = host.raw_database_mut(); db.trigger_lru_eviction(); diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 007725be74..2371f7a656 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -98,6 +98,13 @@ config_data! { /// Code's `files.watcherExclude`. files_exclude | files_excludeDirs: Vec<Utf8PathBuf> = vec![], + /// This config controls the frequency in which rust-analyzer will perform its internal Garbage + /// Collection. It is specified in revisions, roughly equivalent to number of changes. The default + /// is 1000. + /// + /// Setting a smaller value may help limit peak memory usage at the expense of speed. + gc_frequency: usize = 1000, + /// If this is `true`, when "Goto Implementations" and in "Implementations" lens, are triggered on a `struct` or `enum` or `union`, we filter out trait implementations that originate from `derive`s above the type. gotoImplementations_filterAdjacentDerives: bool = false, @@ -1701,9 +1708,11 @@ impl Config { pub fn caps(&self) -> &ClientCapabilities { &self.caps } -} -impl Config { + pub fn gc_freq(&self) -> usize { + *self.gc_frequency() + } + pub fn assist(&self, source_root: Option<SourceRootId>) -> AssistConfig { AssistConfig { snippet_cap: self.snippet_cap(), diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs index 7828f50844..41783584a9 100644 --- a/crates/rust-analyzer/src/global_state.rs +++ b/crates/rust-analyzer/src/global_state.rs @@ -193,6 +193,8 @@ pub(crate) struct GlobalState { /// which will usually end up causing a bunch of incorrect diagnostics on startup. pub(crate) incomplete_crate_graph: bool, + pub(crate) revisions_until_next_gc: usize, + pub(crate) minicore: MiniCoreRustAnalyzerInternalOnly, } @@ -319,6 +321,8 @@ impl GlobalState { incomplete_crate_graph: false, minicore: MiniCoreRustAnalyzerInternalOnly::default(), + + revisions_until_next_gc: config.gc_freq(), }; // Apply any required database inputs from the config. this.update_configuration(config); @@ -435,6 +439,15 @@ impl GlobalState { }); self.analysis_host.apply_change(change); + + if self.revisions_until_next_gc == 0 { + // SAFETY: Just changed some database inputs, all queries were canceled. + unsafe { hir::collect_ty_garbage() }; + self.revisions_until_next_gc = self.config.gc_freq(); + } else { + self.revisions_until_next_gc -= 1; + } + if !modified_ratoml_files.is_empty() || !self.config.same_source_root_parent_map(&self.local_roots_parent_map) { diff --git a/docs/book/src/configuration_generated.md b/docs/book/src/configuration_generated.md index 1f5c672233..6b7ef04964 100644 --- a/docs/book/src/configuration_generated.md +++ b/docs/book/src/configuration_generated.md @@ -635,6 +635,17 @@ Default: `"client"` Controls file watching implementation. +## rust-analyzer.gc.frequency {#gc.frequency} + +Default: `1000` + +This config controls the frequency in which rust-analyzer will perform its internal Garbage +Collection. It is specified in revisions, roughly equivalent to number of changes. The default +is 1000. + +Setting a smaller value may help limit peak memory usage at the expense of speed. + + ## rust-analyzer.gotoImplementations.filterAdjacentDerives {#gotoImplementations.filterAdjacentDerives} Default: `false` diff --git a/editors/code/package.json b/editors/code/package.json index 98fe6a558b..97db1322dd 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -1628,6 +1628,17 @@ } }, { + "title": "Gc", + "properties": { + "rust-analyzer.gc.frequency": { + "markdownDescription": "This config controls the frequency in which rust-analyzer will perform its internal Garbage\nCollection. It is specified in revisions, roughly equivalent to number of changes. The default\nis 1000.\n\nSetting a smaller value may help limit peak memory usage at the expense of speed.", + "default": 1000, + "type": "integer", + "minimum": 0 + } + } + }, + { "title": "Goto Implementations", "properties": { "rust-analyzer.gotoImplementations.filterAdjacentDerives": { |