Skip to content

Commit 83b3bfc

Browse files
committed
Auto merge of #157252 - heinwol:symbol-Interner-double-hashing, r=petrochenkov
Rewrite `rustc_span::symbol::Interner` to avoid double hashing Involves resorting to raw `HashTable` and writing an ad-hoc `IndexMap`-like structure, as we cannot get access to raw hashes otherwise. My local cachegrind profile shows ~ -20_000_000 Ir r? @petrochenkov
2 parents cb46fbb + 00d08cb commit 83b3bfc

2 files changed

Lines changed: 62 additions & 34 deletions

File tree

compiler/rustc_span/src/symbol.rs

Lines changed: 51 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
//! allows bidirectional lookup; i.e., given a value, one can easily find the
33
//! type, and vice versa.
44
5-
use std::hash::{Hash, Hasher};
5+
use std::hash::{BuildHasher, Hash, Hasher};
66
use std::{fmt, str};
77

88
use rustc_arena::DroplessArena;
9-
use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
9+
use rustc_data_structures::fx::FxBuildHasher;
10+
use rustc_data_structures::hash_table::{Entry, HashTable};
1011
use rustc_data_structures::stable_hash::{StableCompare, StableHash, StableHashCtxt, StableHasher};
1112
use rustc_data_structures::sync::Lock;
1213
use rustc_macros::{Decodable, Encodable, StableHash, symbols};
@@ -2744,32 +2745,43 @@ pub(crate) struct Interner(Lock<InternerInner>);
27442745
// between `Interner`s.
27452746
struct InternerInner {
27462747
arena: DroplessArena,
2747-
byte_strs: FxIndexSet<&'static [u8]>,
2748+
indices: HashTable<(&'static [u8], u32)>,
2749+
byte_strs: Vec<&'static [u8]>,
27482750
}
27492751

27502752
impl Interner {
27512753
// These arguments are `&str`, but because of the sharing, we are
27522754
// effectively pre-interning all these strings for both `Symbol` and
27532755
// `ByteSymbol`.
27542756
fn prefill(init: &[&'static str], extra: &[&'static str]) -> Self {
2755-
let byte_strs = FxIndexSet::from_iter(
2756-
init.iter().copied().chain(extra.iter().copied()).map(|str| str.as_bytes()),
2757-
);
2757+
let values = init.iter().copied().chain(extra.iter().copied()).map(|str| str.as_bytes());
2758+
let (size_hint, _) = values.size_hint();
2759+
let mut conflicting_values: Vec<&[u8]> = Vec::new();
27582760

2759-
// The order in which duplicates are reported is irrelevant.
2760-
#[expect(rustc::potential_query_instability)]
2761-
if byte_strs.len() != init.len() + extra.len() {
2761+
let mut indices: HashTable<(&'static [u8], u32)> = HashTable::with_capacity(size_hint);
2762+
let hasher = FxBuildHasher::default();
2763+
2764+
let mut byte_strs: Vec<&'static [u8]> = Vec::with_capacity(size_hint);
2765+
2766+
for v in values {
2767+
match indices.entry(hasher.hash_one(&v), |&(s, _)| s == v, |&(s, _)| hasher.hash_one(s))
2768+
{
2769+
Entry::Occupied(v) => conflicting_values.push(v.get().0),
2770+
Entry::Vacant(view) => {
2771+
view.insert((v, byte_strs.len() as u32));
2772+
byte_strs.push(v);
2773+
}
2774+
}
2775+
}
2776+
2777+
if conflicting_values.len() != 0 {
27622778
panic!(
27632779
"duplicate symbols in the rustc symbol list and the extra symbols added by the driver: {:?}",
2764-
FxHashSet::intersection(
2765-
&init.iter().copied().collect(),
2766-
&extra.iter().copied().collect(),
2767-
)
2768-
.collect::<Vec<_>>()
2780+
conflicting_values
27692781
)
27702782
}
27712783

2772-
Interner(Lock::new(InternerInner { arena: Default::default(), byte_strs }))
2784+
Interner(Lock::new(InternerInner { arena: Default::default(), indices, byte_strs }))
27732785
}
27742786

27752787
fn intern_str(&self, str: &str) -> Symbol {
@@ -2782,24 +2794,29 @@ impl Interner {
27822794

27832795
#[inline]
27842796
fn intern_inner(&self, byte_str: &[u8]) -> u32 {
2785-
let mut inner = self.0.lock();
2786-
if let Some(idx) = inner.byte_strs.get_index_of(byte_str) {
2787-
return idx as u32;
2788-
}
2789-
2790-
let byte_str: &[u8] = inner.arena.alloc_slice(byte_str);
2791-
2792-
// SAFETY: we can extend the arena allocation to `'static` because we
2793-
// only access these while the arena is still alive.
2794-
let byte_str: &'static [u8] = unsafe { &*(byte_str as *const [u8]) };
2795-
2796-
// This second hash table lookup can be avoided by using `RawEntryMut`,
2797-
// but this code path isn't hot enough for it to be worth it. See
2798-
// #91445 for details.
2799-
let (idx, is_new) = inner.byte_strs.insert_full(byte_str);
2800-
debug_assert!(is_new); // due to the get_index_of check above
2801-
2802-
idx as u32
2797+
let hasher = FxBuildHasher::default();
2798+
let hash_of_byte_str = hasher.hash_one(byte_str);
2799+
2800+
self.0.with_lock(|inner| {
2801+
match inner.indices.entry(
2802+
hash_of_byte_str,
2803+
|&(s, _)| s == byte_str,
2804+
|&(s, _)| hasher.hash_one(s),
2805+
) {
2806+
Entry::Occupied(v) => v.get().1,
2807+
Entry::Vacant(view) => {
2808+
let byte_str: &[u8] = inner.arena.alloc_slice(byte_str);
2809+
2810+
// SAFETY: we can extend the arena allocation to `'static` because we
2811+
// only access these while the arena is still alive.
2812+
let byte_str: &'static [u8] = unsafe { &*(byte_str as *const [u8]) };
2813+
let idx = inner.byte_strs.len() as u32;
2814+
view.insert((byte_str, idx));
2815+
inner.byte_strs.push(byte_str);
2816+
idx
2817+
}
2818+
}
2819+
})
28032820
}
28042821

28052822
/// Get the symbol as a string.
@@ -2819,7 +2836,7 @@ impl Interner {
28192836
}
28202837

28212838
fn get_inner(&self, index: usize) -> &[u8] {
2822-
self.0.lock().byte_strs.get_index(index).unwrap()
2839+
self.0.with_lock(|inner| inner.byte_strs[index])
28232840
}
28242841
}
28252842

compiler/rustc_span/src/symbol/tests.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,17 @@ fn interner_tests() {
1515
assert_eq!(i.intern_str("dog"), Symbol::new(0));
1616
}
1717

18+
#[test]
19+
fn interner_get() {
20+
let i = Interner::prefill(&["chicken"], &["cow"]);
21+
let dog_idx = i.intern_str("dog"); // 2
22+
let cat_idx = i.intern_str("cat"); // 3
23+
assert_eq!(i.get_str(Symbol::new(0)), "chicken");
24+
assert_eq!(i.get_str(Symbol::new(1)), "cow");
25+
assert_eq!(i.get_str(cat_idx), "cat");
26+
assert_eq!(i.get_str(dog_idx), "dog");
27+
}
28+
1829
#[test]
1930
fn without_first_quote_test() {
2031
create_default_session_globals_then(|| {

0 commit comments

Comments
 (0)