Skip to content

Commit 5dac463

Browse files
Upgrade dashmap and hashbrown
And adapt `intern` to the changes in the API.
1 parent 42f6877 commit 5dac463

File tree

5 files changed

+103
-136
lines changed

5 files changed

+103
-136
lines changed

src/tools/rust-analyzer/Cargo.lock

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ name = "base-db"
7979
version = "0.0.0"
8080
dependencies = [
8181
"cfg",
82-
"dashmap 5.5.3",
82+
"dashmap",
8383
"intern",
8484
"la-arena 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
8585
"query-group-macro",
@@ -323,19 +323,6 @@ dependencies = [
323323
"windows-sys 0.59.0",
324324
]
325325

326-
[[package]]
327-
name = "dashmap"
328-
version = "5.5.3"
329-
source = "registry+https://github.com/rust-lang/crates.io-index"
330-
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
331-
dependencies = [
332-
"cfg-if",
333-
"hashbrown 0.14.5",
334-
"lock_api",
335-
"once_cell",
336-
"parking_lot_core",
337-
]
338-
339326
[[package]]
340327
name = "dashmap"
341328
version = "6.1.0"
@@ -1051,7 +1038,7 @@ dependencies = [
10511038
name = "intern"
10521039
version = "0.0.0"
10531040
dependencies = [
1054-
"dashmap 5.5.3",
1041+
"dashmap",
10551042
"hashbrown 0.14.5",
10561043
"rustc-hash 2.1.1",
10571044
"triomphe",
@@ -2045,7 +2032,7 @@ checksum = "1be22155f8d9732518b2db2bf379fe6f0b2375e76b08b7c8fe6c1b887d548c24"
20452032
dependencies = [
20462033
"boxcar",
20472034
"crossbeam-queue",
2048-
"dashmap 6.1.0",
2035+
"dashmap",
20492036
"hashbrown 0.15.2",
20502037
"hashlink",
20512038
"indexmap",

src/tools/rust-analyzer/Cargo.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,11 +158,10 @@ triomphe = { version = "0.1.14", default-features = false, features = ["std"] }
158158
url = "2.5.4"
159159
xshell = "0.2.7"
160160

161-
162161
# We need to freeze the version of the crate, as the raw-api feature is considered unstable
163-
dashmap = { version = "=5.5.3", features = ["raw-api"] }
162+
dashmap = { version = "=6.1.0", features = ["raw-api", "inline"] }
164163
# We need to freeze the version of the crate, as it needs to match with dashmap
165-
hashbrown = { version = "=0.14.5", features = [
164+
hashbrown = { version = "0.14.0", features = [
166165
"inline-more",
167166
], default-features = false }
168167

src/tools/rust-analyzer/crates/intern/src/lib.rs

Lines changed: 44 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,20 @@
33
//! Eventually this should probably be replaced with salsa-based interning.
44
55
use std::{
6+
borrow::Borrow,
67
fmt::{self, Debug, Display},
7-
hash::{BuildHasherDefault, Hash, Hasher},
8+
hash::{BuildHasher, BuildHasherDefault, Hash, Hasher},
89
ops::Deref,
910
sync::OnceLock,
1011
};
1112

1213
use dashmap::{DashMap, SharedValue};
13-
use hashbrown::{HashMap, hash_map::RawEntryMut};
14+
use hashbrown::raw::RawTable;
1415
use rustc_hash::FxHasher;
1516
use triomphe::Arc;
1617

1718
type InternMap<T> = DashMap<Arc<T>, (), BuildHasherDefault<FxHasher>>;
18-
type Guard<T> = dashmap::RwLockWriteGuard<
19-
'static,
20-
HashMap<Arc<T>, SharedValue<()>, BuildHasherDefault<FxHasher>>,
21-
>;
19+
type Guard<T> = dashmap::RwLockWriteGuard<'static, RawTable<(Arc<T>, SharedValue<()>)>>;
2220

2321
mod symbol;
2422
pub use self::symbol::{Symbol, symbols as sym};
@@ -28,54 +26,61 @@ pub struct Interned<T: Internable + ?Sized> {
2826
}
2927

3028
impl<T: Internable> Interned<T> {
29+
#[inline]
3130
pub fn new(obj: T) -> Self {
32-
let (mut shard, hash) = Self::select(&obj);
33-
// Atomically,
34-
// - check if `obj` is already in the map
35-
// - if so, clone its `Arc` and return it
36-
// - if not, box it up, insert it, and return a clone
37-
// This needs to be atomic (locking the shard) to avoid races with other thread, which could
38-
// insert the same object between us looking it up and inserting it.
39-
match shard.raw_entry_mut().from_key_hashed_nocheck(hash, &obj) {
40-
RawEntryMut::Occupied(occ) => Self { arc: occ.key().clone() },
41-
RawEntryMut::Vacant(vac) => Self {
42-
arc: vac.insert_hashed_nocheck(hash, Arc::new(obj), SharedValue::new(())).0.clone(),
43-
},
44-
}
31+
Self::new_generic(obj)
4532
}
4633
}
4734

4835
impl Interned<str> {
36+
#[inline]
4937
pub fn new_str(s: &str) -> Self {
50-
let (mut shard, hash) = Self::select(s);
38+
Self::new_generic(s)
39+
}
40+
}
41+
42+
impl<T: Internable + ?Sized> Interned<T> {
43+
#[inline]
44+
pub fn new_generic<U>(obj: U) -> Self
45+
where
46+
U: Borrow<T>,
47+
Arc<T>: From<U>,
48+
{
49+
let storage = T::storage().get();
50+
let (mut shard, hash) = Self::select(storage, obj.borrow());
5151
// Atomically,
5252
// - check if `obj` is already in the map
5353
// - if so, clone its `Arc` and return it
5454
// - if not, box it up, insert it, and return a clone
5555
// This needs to be atomic (locking the shard) to avoid races with other thread, which could
5656
// insert the same object between us looking it up and inserting it.
57-
match shard.raw_entry_mut().from_key_hashed_nocheck(hash, s) {
58-
RawEntryMut::Occupied(occ) => Self { arc: occ.key().clone() },
59-
RawEntryMut::Vacant(vac) => Self {
60-
arc: vac.insert_hashed_nocheck(hash, Arc::from(s), SharedValue::new(())).0.clone(),
57+
let bucket = match shard.find_or_find_insert_slot(
58+
hash,
59+
|(other, _)| **other == *obj.borrow(),
60+
|(x, _)| Self::hash(storage, x),
61+
) {
62+
Ok(bucket) => bucket,
63+
// SAFETY: The slot came from `find_or_find_insert_slot()`, and the table wasn't modified since then.
64+
Err(insert_slot) => unsafe {
65+
shard.insert_in_slot(hash, insert_slot, (Arc::from(obj), SharedValue::new(())))
6166
},
62-
}
67+
};
68+
// SAFETY: We just retrieved/inserted this bucket.
69+
unsafe { Self { arc: bucket.as_ref().0.clone() } }
6370
}
64-
}
6571

66-
impl<T: Internable + ?Sized> Interned<T> {
6772
#[inline]
68-
fn select(obj: &T) -> (Guard<T>, u64) {
69-
let storage = T::storage().get();
70-
let hash = {
71-
let mut hasher = std::hash::BuildHasher::build_hasher(storage.hasher());
72-
obj.hash(&mut hasher);
73-
hasher.finish()
74-
};
73+
fn select(storage: &'static InternMap<T>, obj: &T) -> (Guard<T>, u64) {
74+
let hash = Self::hash(storage, obj);
7575
let shard_idx = storage.determine_shard(hash as usize);
7676
let shard = &storage.shards()[shard_idx];
7777
(shard.write(), hash)
7878
}
79+
80+
#[inline]
81+
fn hash(storage: &'static InternMap<T>, obj: &T) -> u64 {
82+
storage.hasher().hash_one(obj)
83+
}
7984
}
8085

8186
impl<T: Internable + ?Sized> Drop for Interned<T> {
@@ -93,21 +98,20 @@ impl<T: Internable + ?Sized> Drop for Interned<T> {
9398
impl<T: Internable + ?Sized> Interned<T> {
9499
#[cold]
95100
fn drop_slow(&mut self) {
96-
let (mut shard, hash) = Self::select(&self.arc);
101+
let storage = T::storage().get();
102+
let (mut shard, hash) = Self::select(storage, &self.arc);
97103

98104
if Arc::count(&self.arc) != 2 {
99105
// Another thread has interned another copy
100106
return;
101107
}
102108

103-
match shard.raw_entry_mut().from_key_hashed_nocheck(hash, &self.arc) {
104-
RawEntryMut::Occupied(occ) => occ.remove(),
105-
RawEntryMut::Vacant(_) => unreachable!(),
106-
};
109+
shard.remove_entry(hash, |(other, _)| **other == *self.arc);
107110

108111
// Shrink the backing storage if the shard is less than 50% occupied.
109112
if shard.len() * 2 < shard.capacity() {
110-
shard.shrink_to_fit();
113+
let len = shard.len();
114+
shard.shrink_to(len, |(x, _)| Self::hash(storage, x));
111115
}
112116
}
113117
}

src/tools/rust-analyzer/crates/intern/src/symbol.rs

Lines changed: 42 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@
22
//! supporting compile time declaration of symbols that will never be freed.
33
44
use std::{
5-
borrow::Borrow,
65
fmt,
7-
hash::{BuildHasherDefault, Hash, Hasher},
6+
hash::{BuildHasher, BuildHasherDefault, Hash},
87
mem::{self, ManuallyDrop},
98
ptr::NonNull,
109
sync::OnceLock,
1110
};
1211

1312
use dashmap::{DashMap, SharedValue};
14-
use hashbrown::{HashMap, hash_map::RawEntryMut};
13+
use hashbrown::raw::RawTable;
1514
use rustc_hash::FxHasher;
1615
use triomphe::Arc;
1716

@@ -127,31 +126,39 @@ impl fmt::Debug for Symbol {
127126
const _: () = assert!(size_of::<Symbol>() == size_of::<NonNull<()>>());
128127
const _: () = assert!(align_of::<Symbol>() == align_of::<NonNull<()>>());
129128

130-
static MAP: OnceLock<DashMap<SymbolProxy, (), BuildHasherDefault<FxHasher>>> = OnceLock::new();
129+
type Map = DashMap<Symbol, (), BuildHasherDefault<FxHasher>>;
130+
static MAP: OnceLock<Map> = OnceLock::new();
131131

132132
impl Symbol {
133133
pub fn intern(s: &str) -> Self {
134-
let (mut shard, hash) = Self::select_shard(s);
134+
let storage = MAP.get_or_init(symbols::prefill);
135+
let (mut shard, hash) = Self::select_shard(storage, s);
135136
// Atomically,
136137
// - check if `obj` is already in the map
137138
// - if so, copy out its entry, conditionally bumping the backing Arc and return it
138139
// - if not, put it into a box and then into an Arc, insert it, bump the ref-count and return the copy
139140
// This needs to be atomic (locking the shard) to avoid races with other thread, which could
140141
// insert the same object between us looking it up and inserting it.
141-
match shard.raw_entry_mut().from_key_hashed_nocheck(hash, s) {
142-
RawEntryMut::Occupied(occ) => Self { repr: increase_arc_refcount(occ.key().0) },
143-
RawEntryMut::Vacant(vac) => Self {
144-
repr: increase_arc_refcount(
145-
vac.insert_hashed_nocheck(
146-
hash,
147-
SymbolProxy(TaggedArcPtr::arc(Arc::new(Box::<str>::from(s)))),
142+
let bucket = match shard.find_or_find_insert_slot(
143+
hash,
144+
|(other, _)| other.as_str() == s,
145+
|(x, _)| Self::hash(storage, x.as_str()),
146+
) {
147+
Ok(bucket) => bucket,
148+
// SAFETY: The slot came from `find_or_find_insert_slot()`, and the table wasn't modified since then.
149+
Err(insert_slot) => unsafe {
150+
shard.insert_in_slot(
151+
hash,
152+
insert_slot,
153+
(
154+
Symbol { repr: TaggedArcPtr::arc(Arc::new(Box::<str>::from(s))) },
148155
SharedValue::new(()),
149-
)
150-
.0
151-
.0,
152-
),
156+
),
157+
)
153158
},
154-
}
159+
};
160+
// SAFETY: We just retrieved/inserted this bucket.
161+
unsafe { bucket.as_ref().0.clone() }
155162
}
156163

157164
pub fn integer(i: usize) -> Self {
@@ -180,58 +187,52 @@ impl Symbol {
180187
symbols::__empty.clone()
181188
}
182189

190+
#[inline]
183191
pub fn as_str(&self) -> &str {
184192
self.repr.as_str()
185193
}
186194

187195
#[inline]
188196
fn select_shard(
197+
storage: &'static Map,
189198
s: &str,
190-
) -> (
191-
dashmap::RwLockWriteGuard<
192-
'static,
193-
HashMap<SymbolProxy, SharedValue<()>, BuildHasherDefault<FxHasher>>,
194-
>,
195-
u64,
196-
) {
197-
let storage = MAP.get_or_init(symbols::prefill);
198-
let hash = {
199-
let mut hasher = std::hash::BuildHasher::build_hasher(storage.hasher());
200-
s.hash(&mut hasher);
201-
hasher.finish()
202-
};
199+
) -> (dashmap::RwLockWriteGuard<'static, RawTable<(Symbol, SharedValue<()>)>>, u64) {
200+
let hash = Self::hash(storage, s);
203201
let shard_idx = storage.determine_shard(hash as usize);
204202
let shard = &storage.shards()[shard_idx];
205203
(shard.write(), hash)
206204
}
207205

206+
#[inline]
207+
fn hash(storage: &'static Map, s: &str) -> u64 {
208+
storage.hasher().hash_one(s)
209+
}
210+
208211
#[cold]
209212
fn drop_slow(arc: &Arc<Box<str>>) {
210-
let (mut shard, hash) = Self::select_shard(arc);
213+
let storage = MAP.get_or_init(symbols::prefill);
214+
let (mut shard, hash) = Self::select_shard(storage, arc);
211215

212216
match Arc::count(arc) {
213-
0 => unreachable!(),
214-
1 => unreachable!(),
217+
0 | 1 => unreachable!(),
215218
2 => (),
216219
_ => {
217220
// Another thread has interned another copy
218221
return;
219222
}
220223
}
221224

222-
let ptr = match shard.raw_entry_mut().from_key_hashed_nocheck::<str>(hash, arc.as_ref()) {
223-
RawEntryMut::Occupied(occ) => occ.remove_entry(),
224-
RawEntryMut::Vacant(_) => unreachable!(),
225-
}
226-
.0
227-
.0;
225+
let s = &***arc;
226+
let (ptr, _) = shard.remove_entry(hash, |(x, _)| x.as_str() == s).unwrap();
227+
let ptr = ManuallyDrop::new(ptr);
228228
// SAFETY: We're dropping, we have ownership.
229-
ManuallyDrop::into_inner(unsafe { ptr.try_as_arc_owned().unwrap() });
229+
ManuallyDrop::into_inner(unsafe { ptr.repr.try_as_arc_owned().unwrap() });
230230
debug_assert_eq!(Arc::count(arc), 1);
231231

232232
// Shrink the backing storage if the shard is less than 50% occupied.
233233
if shard.len() * 2 < shard.capacity() {
234-
shard.shrink_to_fit();
234+
let len = shard.len();
235+
shard.shrink_to(len, |(x, _)| Self::hash(storage, x.as_str()));
235236
}
236237
}
237238
}
@@ -276,22 +277,6 @@ impl fmt::Display for Symbol {
276277
}
277278
}
278279

279-
// only exists so we can use `from_key_hashed_nocheck` with a &str
280-
#[derive(Debug, PartialEq, Eq)]
281-
struct SymbolProxy(TaggedArcPtr);
282-
283-
impl Hash for SymbolProxy {
284-
fn hash<H: Hasher>(&self, state: &mut H) {
285-
self.0.as_str().hash(state);
286-
}
287-
}
288-
289-
impl Borrow<str> for SymbolProxy {
290-
fn borrow(&self) -> &str {
291-
self.0.as_str()
292-
}
293-
}
294-
295280
#[cfg(test)]
296281
mod tests {
297282
use super::*;

0 commit comments

Comments
 (0)