mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-11-02 04:48:13 +00:00
Upgrade dashmap and hashbrown
And adapt `intern` to the changes in the API.
This commit is contained in:
parent
1fe060719a
commit
4d95ae52f8
5 changed files with 106 additions and 139 deletions
|
|
@ -3,22 +3,20 @@
|
|||
//! Eventually this should probably be replaced with salsa-based interning.
|
||||
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
fmt::{self, Debug, Display},
|
||||
hash::{BuildHasherDefault, Hash, Hasher},
|
||||
hash::{BuildHasher, BuildHasherDefault, Hash, Hasher},
|
||||
ops::Deref,
|
||||
sync::OnceLock,
|
||||
};
|
||||
|
||||
use dashmap::{DashMap, SharedValue};
|
||||
use hashbrown::{HashMap, hash_map::RawEntryMut};
|
||||
use hashbrown::raw::RawTable;
|
||||
use rustc_hash::FxHasher;
|
||||
use triomphe::Arc;
|
||||
|
||||
type InternMap<T> = DashMap<Arc<T>, (), BuildHasherDefault<FxHasher>>;
|
||||
type Guard<T> = dashmap::RwLockWriteGuard<
|
||||
'static,
|
||||
HashMap<Arc<T>, SharedValue<()>, BuildHasherDefault<FxHasher>>,
|
||||
>;
|
||||
type Guard<T> = dashmap::RwLockWriteGuard<'static, RawTable<(Arc<T>, SharedValue<()>)>>;
|
||||
|
||||
mod symbol;
|
||||
pub use self::symbol::{Symbol, symbols as sym};
|
||||
|
|
@ -28,54 +26,61 @@ pub struct Interned<T: Internable + ?Sized> {
|
|||
}
|
||||
|
||||
impl<T: Internable> Interned<T> {
|
||||
#[inline]
|
||||
pub fn new(obj: T) -> Self {
|
||||
let (mut shard, hash) = Self::select(&obj);
|
||||
// Atomically,
|
||||
// - check if `obj` is already in the map
|
||||
// - if so, clone its `Arc` and return it
|
||||
// - if not, box it up, insert it, and return a clone
|
||||
// This needs to be atomic (locking the shard) to avoid races with other thread, which could
|
||||
// insert the same object between us looking it up and inserting it.
|
||||
match shard.raw_entry_mut().from_key_hashed_nocheck(hash, &obj) {
|
||||
RawEntryMut::Occupied(occ) => Self { arc: occ.key().clone() },
|
||||
RawEntryMut::Vacant(vac) => Self {
|
||||
arc: vac.insert_hashed_nocheck(hash, Arc::new(obj), SharedValue::new(())).0.clone(),
|
||||
},
|
||||
}
|
||||
Self::new_generic(obj)
|
||||
}
|
||||
}
|
||||
|
||||
impl Interned<str> {
|
||||
#[inline]
|
||||
pub fn new_str(s: &str) -> Self {
|
||||
let (mut shard, hash) = Self::select(s);
|
||||
// Atomically,
|
||||
// - check if `obj` is already in the map
|
||||
// - if so, clone its `Arc` and return it
|
||||
// - if not, box it up, insert it, and return a clone
|
||||
// This needs to be atomic (locking the shard) to avoid races with other thread, which could
|
||||
// insert the same object between us looking it up and inserting it.
|
||||
match shard.raw_entry_mut().from_key_hashed_nocheck(hash, s) {
|
||||
RawEntryMut::Occupied(occ) => Self { arc: occ.key().clone() },
|
||||
RawEntryMut::Vacant(vac) => Self {
|
||||
arc: vac.insert_hashed_nocheck(hash, Arc::from(s), SharedValue::new(())).0.clone(),
|
||||
},
|
||||
}
|
||||
Self::new_generic(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Internable + ?Sized> Interned<T> {
|
||||
#[inline]
|
||||
fn select(obj: &T) -> (Guard<T>, u64) {
|
||||
pub fn new_generic<U>(obj: U) -> Self
|
||||
where
|
||||
U: Borrow<T>,
|
||||
Arc<T>: From<U>,
|
||||
{
|
||||
let storage = T::storage().get();
|
||||
let hash = {
|
||||
let mut hasher = std::hash::BuildHasher::build_hasher(storage.hasher());
|
||||
obj.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
let (mut shard, hash) = Self::select(storage, obj.borrow());
|
||||
// Atomically,
|
||||
// - check if `obj` is already in the map
|
||||
// - if so, clone its `Arc` and return it
|
||||
// - if not, box it up, insert it, and return a clone
|
||||
// This needs to be atomic (locking the shard) to avoid races with other thread, which could
|
||||
// insert the same object between us looking it up and inserting it.
|
||||
let bucket = match shard.find_or_find_insert_slot(
|
||||
hash,
|
||||
|(other, _)| **other == *obj.borrow(),
|
||||
|(x, _)| Self::hash(storage, x),
|
||||
) {
|
||||
Ok(bucket) => bucket,
|
||||
// SAFETY: The slot came from `find_or_find_insert_slot()`, and the table wasn't modified since then.
|
||||
Err(insert_slot) => unsafe {
|
||||
shard.insert_in_slot(hash, insert_slot, (Arc::from(obj), SharedValue::new(())))
|
||||
},
|
||||
};
|
||||
// SAFETY: We just retrieved/inserted this bucket.
|
||||
unsafe { Self { arc: bucket.as_ref().0.clone() } }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn select(storage: &'static InternMap<T>, obj: &T) -> (Guard<T>, u64) {
|
||||
let hash = Self::hash(storage, obj);
|
||||
let shard_idx = storage.determine_shard(hash as usize);
|
||||
let shard = &storage.shards()[shard_idx];
|
||||
(shard.write(), hash)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn hash(storage: &'static InternMap<T>, obj: &T) -> u64 {
|
||||
storage.hasher().hash_one(obj)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Internable + ?Sized> Drop for Interned<T> {
|
||||
|
|
@ -93,21 +98,20 @@ impl<T: Internable + ?Sized> Drop for Interned<T> {
|
|||
impl<T: Internable + ?Sized> Interned<T> {
|
||||
#[cold]
|
||||
fn drop_slow(&mut self) {
|
||||
let (mut shard, hash) = Self::select(&self.arc);
|
||||
let storage = T::storage().get();
|
||||
let (mut shard, hash) = Self::select(storage, &self.arc);
|
||||
|
||||
if Arc::count(&self.arc) != 2 {
|
||||
// Another thread has interned another copy
|
||||
return;
|
||||
}
|
||||
|
||||
match shard.raw_entry_mut().from_key_hashed_nocheck(hash, &self.arc) {
|
||||
RawEntryMut::Occupied(occ) => occ.remove(),
|
||||
RawEntryMut::Vacant(_) => unreachable!(),
|
||||
};
|
||||
shard.remove_entry(hash, |(other, _)| **other == *self.arc);
|
||||
|
||||
// Shrink the backing storage if the shard is less than 50% occupied.
|
||||
if shard.len() * 2 < shard.capacity() {
|
||||
shard.shrink_to_fit();
|
||||
let len = shard.len();
|
||||
shard.shrink_to(len, |(x, _)| Self::hash(storage, x));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,16 +2,15 @@
|
|||
//! supporting compile time declaration of symbols that will never be freed.
|
||||
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
fmt,
|
||||
hash::{BuildHasherDefault, Hash, Hasher},
|
||||
hash::{BuildHasher, BuildHasherDefault, Hash},
|
||||
mem::{self, ManuallyDrop},
|
||||
ptr::NonNull,
|
||||
sync::OnceLock,
|
||||
};
|
||||
|
||||
use dashmap::{DashMap, SharedValue};
|
||||
use hashbrown::{HashMap, hash_map::RawEntryMut};
|
||||
use hashbrown::raw::RawTable;
|
||||
use rustc_hash::FxHasher;
|
||||
use triomphe::Arc;
|
||||
|
||||
|
|
@ -127,31 +126,39 @@ impl fmt::Debug for Symbol {
|
|||
const _: () = assert!(size_of::<Symbol>() == size_of::<NonNull<()>>());
|
||||
const _: () = assert!(align_of::<Symbol>() == align_of::<NonNull<()>>());
|
||||
|
||||
static MAP: OnceLock<DashMap<SymbolProxy, (), BuildHasherDefault<FxHasher>>> = OnceLock::new();
|
||||
type Map = DashMap<Symbol, (), BuildHasherDefault<FxHasher>>;
|
||||
static MAP: OnceLock<Map> = OnceLock::new();
|
||||
|
||||
impl Symbol {
|
||||
pub fn intern(s: &str) -> Self {
|
||||
let (mut shard, hash) = Self::select_shard(s);
|
||||
let storage = MAP.get_or_init(symbols::prefill);
|
||||
let (mut shard, hash) = Self::select_shard(storage, s);
|
||||
// Atomically,
|
||||
// - check if `obj` is already in the map
|
||||
// - if so, copy out its entry, conditionally bumping the backing Arc and return it
|
||||
// - if not, put it into a box and then into an Arc, insert it, bump the ref-count and return the copy
|
||||
// This needs to be atomic (locking the shard) to avoid races with other thread, which could
|
||||
// insert the same object between us looking it up and inserting it.
|
||||
match shard.raw_entry_mut().from_key_hashed_nocheck(hash, s) {
|
||||
RawEntryMut::Occupied(occ) => Self { repr: increase_arc_refcount(occ.key().0) },
|
||||
RawEntryMut::Vacant(vac) => Self {
|
||||
repr: increase_arc_refcount(
|
||||
vac.insert_hashed_nocheck(
|
||||
hash,
|
||||
SymbolProxy(TaggedArcPtr::arc(Arc::new(Box::<str>::from(s)))),
|
||||
let bucket = match shard.find_or_find_insert_slot(
|
||||
hash,
|
||||
|(other, _)| other.as_str() == s,
|
||||
|(x, _)| Self::hash(storage, x.as_str()),
|
||||
) {
|
||||
Ok(bucket) => bucket,
|
||||
// SAFETY: The slot came from `find_or_find_insert_slot()`, and the table wasn't modified since then.
|
||||
Err(insert_slot) => unsafe {
|
||||
shard.insert_in_slot(
|
||||
hash,
|
||||
insert_slot,
|
||||
(
|
||||
Symbol { repr: TaggedArcPtr::arc(Arc::new(Box::<str>::from(s))) },
|
||||
SharedValue::new(()),
|
||||
)
|
||||
.0
|
||||
.0,
|
||||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
}
|
||||
};
|
||||
// SAFETY: We just retrieved/inserted this bucket.
|
||||
unsafe { bucket.as_ref().0.clone() }
|
||||
}
|
||||
|
||||
pub fn integer(i: usize) -> Self {
|
||||
|
|
@ -180,38 +187,34 @@ impl Symbol {
|
|||
symbols::__empty.clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.repr.as_str()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn select_shard(
|
||||
storage: &'static Map,
|
||||
s: &str,
|
||||
) -> (
|
||||
dashmap::RwLockWriteGuard<
|
||||
'static,
|
||||
HashMap<SymbolProxy, SharedValue<()>, BuildHasherDefault<FxHasher>>,
|
||||
>,
|
||||
u64,
|
||||
) {
|
||||
let storage = MAP.get_or_init(symbols::prefill);
|
||||
let hash = {
|
||||
let mut hasher = std::hash::BuildHasher::build_hasher(storage.hasher());
|
||||
s.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
};
|
||||
) -> (dashmap::RwLockWriteGuard<'static, RawTable<(Symbol, SharedValue<()>)>>, u64) {
|
||||
let hash = Self::hash(storage, s);
|
||||
let shard_idx = storage.determine_shard(hash as usize);
|
||||
let shard = &storage.shards()[shard_idx];
|
||||
(shard.write(), hash)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn hash(storage: &'static Map, s: &str) -> u64 {
|
||||
storage.hasher().hash_one(s)
|
||||
}
|
||||
|
||||
#[cold]
|
||||
fn drop_slow(arc: &Arc<Box<str>>) {
|
||||
let (mut shard, hash) = Self::select_shard(arc);
|
||||
let storage = MAP.get_or_init(symbols::prefill);
|
||||
let (mut shard, hash) = Self::select_shard(storage, arc);
|
||||
|
||||
match Arc::count(arc) {
|
||||
0 => unreachable!(),
|
||||
1 => unreachable!(),
|
||||
0 | 1 => unreachable!(),
|
||||
2 => (),
|
||||
_ => {
|
||||
// Another thread has interned another copy
|
||||
|
|
@ -219,19 +222,17 @@ impl Symbol {
|
|||
}
|
||||
}
|
||||
|
||||
let ptr = match shard.raw_entry_mut().from_key_hashed_nocheck::<str>(hash, arc.as_ref()) {
|
||||
RawEntryMut::Occupied(occ) => occ.remove_entry(),
|
||||
RawEntryMut::Vacant(_) => unreachable!(),
|
||||
}
|
||||
.0
|
||||
.0;
|
||||
let s = &***arc;
|
||||
let (ptr, _) = shard.remove_entry(hash, |(x, _)| x.as_str() == s).unwrap();
|
||||
let ptr = ManuallyDrop::new(ptr);
|
||||
// SAFETY: We're dropping, we have ownership.
|
||||
ManuallyDrop::into_inner(unsafe { ptr.try_as_arc_owned().unwrap() });
|
||||
ManuallyDrop::into_inner(unsafe { ptr.repr.try_as_arc_owned().unwrap() });
|
||||
debug_assert_eq!(Arc::count(arc), 1);
|
||||
|
||||
// Shrink the backing storage if the shard is less than 50% occupied.
|
||||
if shard.len() * 2 < shard.capacity() {
|
||||
shard.shrink_to_fit();
|
||||
let len = shard.len();
|
||||
shard.shrink_to(len, |(x, _)| Self::hash(storage, x.as_str()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -276,22 +277,6 @@ impl fmt::Display for Symbol {
|
|||
}
|
||||
}
|
||||
|
||||
// only exists so we can use `from_key_hashed_nocheck` with a &str
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct SymbolProxy(TaggedArcPtr);
|
||||
|
||||
impl Hash for SymbolProxy {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.0.as_str().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<str> for SymbolProxy {
|
||||
fn borrow(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
|||
|
|
@ -1,15 +1,12 @@
|
|||
//! Module defining all known symbols required by the rest of rust-analyzer.
|
||||
#![allow(non_upper_case_globals)]
|
||||
|
||||
use std::hash::{BuildHasherDefault, Hash as _, Hasher as _};
|
||||
use std::hash::{BuildHasher, BuildHasherDefault};
|
||||
|
||||
use dashmap::{DashMap, SharedValue};
|
||||
use rustc_hash::FxHasher;
|
||||
|
||||
use crate::{
|
||||
Symbol,
|
||||
symbol::{SymbolProxy, TaggedArcPtr},
|
||||
};
|
||||
use crate::{Symbol, symbol::TaggedArcPtr};
|
||||
|
||||
macro_rules! define_symbols {
|
||||
(@WITH_NAME: $($alias:ident = $value:literal,)* @PLAIN: $($name:ident,)*) => {
|
||||
|
|
@ -28,28 +25,23 @@ macro_rules! define_symbols {
|
|||
)*
|
||||
|
||||
|
||||
pub(super) fn prefill() -> DashMap<SymbolProxy, (), BuildHasherDefault<FxHasher>> {
|
||||
let mut dashmap_ = <DashMap<SymbolProxy, (), BuildHasherDefault<FxHasher>>>::with_hasher(BuildHasherDefault::default());
|
||||
pub(super) fn prefill() -> DashMap<Symbol, (), BuildHasherDefault<FxHasher>> {
|
||||
let mut dashmap_ = <DashMap<Symbol, (), BuildHasherDefault<FxHasher>>>::with_hasher(BuildHasherDefault::default());
|
||||
|
||||
let hash_thing_ = |hasher_: &BuildHasherDefault<FxHasher>, it_: &SymbolProxy| {
|
||||
let mut hasher_ = std::hash::BuildHasher::build_hasher(hasher_);
|
||||
it_.hash(&mut hasher_);
|
||||
hasher_.finish()
|
||||
};
|
||||
let hasher_ = dashmap_.hasher().clone();
|
||||
let hash_one = |it_: &str| hasher_.hash_one(it_);
|
||||
{
|
||||
$(
|
||||
|
||||
let proxy_ = SymbolProxy($name.repr);
|
||||
let hash_ = hash_thing_(dashmap_.hasher(), &proxy_);
|
||||
let s = stringify!($name);
|
||||
let hash_ = hash_one(s);
|
||||
let shard_idx_ = dashmap_.determine_shard(hash_ as usize);
|
||||
dashmap_.shards_mut()[shard_idx_].get_mut().raw_entry_mut().from_hash(hash_, |k| k == &proxy_).insert(proxy_, SharedValue::new(()));
|
||||
dashmap_.shards_mut()[shard_idx_].get_mut().insert(hash_, ($name, SharedValue::new(())), |(x, _)| hash_one(x.as_str()));
|
||||
)*
|
||||
$(
|
||||
|
||||
let proxy_ = SymbolProxy($alias.repr);
|
||||
let hash_ = hash_thing_(dashmap_.hasher(), &proxy_);
|
||||
let s = $value;
|
||||
let hash_ = hash_one(s);
|
||||
let shard_idx_ = dashmap_.determine_shard(hash_ as usize);
|
||||
dashmap_.shards_mut()[shard_idx_].get_mut().raw_entry_mut().from_hash(hash_, |k| k == &proxy_).insert(proxy_, SharedValue::new(()));
|
||||
dashmap_.shards_mut()[shard_idx_].get_mut().insert(hash_, ($alias, SharedValue::new(())), |(x, _)| hash_one(x.as_str()));
|
||||
)*
|
||||
}
|
||||
dashmap_
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue