Unnamed repository; edit this file 'description' to name the repository.
GC support for solver types
A GC is triggered every X revisions, and is synchronous, unfortunately.
Chayim Refael Friedman 4 months ago
parent 776c818 · commit 36c9f62
-rw-r--r--Cargo.lock1
-rw-r--r--crates/hir-ty/src/next_solver/consts.rs24
-rw-r--r--crates/hir-ty/src/next_solver/interner.rs132
-rw-r--r--crates/hir-ty/src/next_solver/opaques.rs5
-rw-r--r--crates/hir-ty/src/next_solver/predicate.rs31
-rw-r--r--crates/hir-ty/src/next_solver/region.rs8
-rw-r--r--crates/hir-ty/src/next_solver/ty.rs8
-rw-r--r--crates/hir/src/lib.rs2
-rw-r--r--crates/intern/Cargo.toml1
-rw-r--r--crates/intern/src/gc.rs325
-rw-r--r--crates/intern/src/intern.rs11
-rw-r--r--crates/intern/src/intern_slice.rs17
-rw-r--r--crates/intern/src/lib.rs2
-rw-r--r--crates/rust-analyzer/src/cli/analysis_stats.rs1
-rw-r--r--crates/rust-analyzer/src/config.rs13
-rw-r--r--crates/rust-analyzer/src/global_state.rs13
-rw-r--r--docs/book/src/configuration_generated.md11
-rw-r--r--editors/code/package.json11
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": {