Skip to content

Commit 63fbff1

Browse files
committed
Make HashTable entries use Tag instead of a full hash
`VacantEntry` now stores a `Tag` instead of a full `hash: u64`. This means that `OccupiedEntry` doesn't need to store anything, because it can get that tag from the control byte for `remove -> VacantEntry`. The `get_bucket_entry` method doesn't need a hash argument either. Also, since `OccupiedEntry` is now smaller, `enum Entry` will be the same size as `VacantEntry` by using a niche for the discriminant. (Although this is not _guaranteed_ by the compiler.)
1 parent 8ad61ce commit 63fbff1

File tree

2 files changed

+54
-35
lines changed

2 files changed

+54
-35
lines changed

src/raw/mod.rs

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,19 @@ impl<T, A: Allocator> RawTable<T, A> {
848848
)
849849
}
850850

851+
/// Removes an element from the table, returning it.
852+
///
853+
/// This also returns an `InsertSlot` pointing to the newly free bucket
854+
/// and the former `Tag` for that bucket.
855+
#[cfg_attr(feature = "inline-more", inline)]
856+
#[allow(clippy::needless_pass_by_value)]
857+
pub(crate) unsafe fn remove_tagged(&mut self, item: Bucket<T>) -> (T, InsertSlot, Tag) {
858+
let index = self.bucket_index(&item);
859+
let tag = *self.table.ctrl(index);
860+
self.table.erase(index);
861+
(item.read(), InsertSlot { index }, tag)
862+
}
863+
851864
/// Finds and removes an element from the table, returning it.
852865
#[cfg_attr(feature = "inline-more", inline)]
853866
pub fn remove_entry(&mut self, hash: u64, eq: impl FnMut(&T) -> bool) -> Option<T> {
@@ -1183,8 +1196,8 @@ impl<T, A: Allocator> RawTable<T, A> {
11831196
}
11841197
}
11851198

1186-
/// Inserts a new element into the table in the given slot, and returns its
1187-
/// raw bucket.
1199+
/// Inserts a new element into the table in the given slot with the given hash,
1200+
/// and returns its raw bucket.
11881201
///
11891202
/// # Safety
11901203
///
@@ -1193,8 +1206,26 @@ impl<T, A: Allocator> RawTable<T, A> {
11931206
/// occurred since that call.
11941207
#[inline]
11951208
pub unsafe fn insert_in_slot(&mut self, hash: u64, slot: InsertSlot, value: T) -> Bucket<T> {
1209+
self.insert_tagged_in_slot(Tag::full(hash), slot, value)
1210+
}
1211+
1212+
/// Inserts a new element into the table in the given slot with the given tag,
1213+
/// and returns its raw bucket.
1214+
///
1215+
/// # Safety
1216+
///
1217+
/// `slot` must point to a slot previously returned by
1218+
/// `find_or_find_insert_slot`, and no mutation of the table must have
1219+
/// occurred since that call.
1220+
#[inline]
1221+
pub(crate) unsafe fn insert_tagged_in_slot(
1222+
&mut self,
1223+
tag: Tag,
1224+
slot: InsertSlot,
1225+
value: T,
1226+
) -> Bucket<T> {
11961227
let old_ctrl = *self.table.ctrl(slot.index);
1197-
self.table.record_item_insert_at(slot.index, old_ctrl, hash);
1228+
self.table.record_item_insert_at(slot.index, old_ctrl, tag);
11981229

11991230
let bucket = self.bucket(slot.index);
12001231
bucket.write(value);
@@ -1269,11 +1300,11 @@ impl<T, A: Allocator> RawTable<T, A> {
12691300
}
12701301

12711302
/// Returns a pointer to an element in the table, but only after verifying that
1272-
/// the index is in-bounds and that its control byte matches the given hash.
1303+
/// the index is in-bounds and the bucket is occupied.
12731304
#[inline]
1274-
pub fn checked_bucket(&self, hash: u64, index: usize) -> Option<Bucket<T>> {
1305+
pub fn checked_bucket(&self, index: usize) -> Option<Bucket<T>> {
12751306
unsafe {
1276-
if index < self.buckets() && *self.table.ctrl(index) == Tag::full(hash) {
1307+
if index < self.buckets() && self.is_bucket_full(index) {
12771308
Some(self.bucket(index))
12781309
} else {
12791310
None
@@ -2456,9 +2487,9 @@ impl RawTableInner {
24562487
}
24572488

24582489
#[inline]
2459-
unsafe fn record_item_insert_at(&mut self, index: usize, old_ctrl: Tag, hash: u64) {
2490+
unsafe fn record_item_insert_at(&mut self, index: usize, old_ctrl: Tag, new_ctrl: Tag) {
24602491
self.growth_left -= usize::from(old_ctrl.special_is_empty());
2461-
self.set_ctrl_hash(index, hash);
2492+
self.set_ctrl(index, new_ctrl);
24622493
self.items += 1;
24632494
}
24642495

src/table.rs

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use core::{fmt, iter::FusedIterator, marker::PhantomData};
22

33
use crate::{
4+
control::Tag,
45
raw::{
56
Allocator, Bucket, Global, InsertSlot, RawDrain, RawExtractIf, RawIntoIter, RawIter,
67
RawIterHash, RawTable,
@@ -303,7 +304,6 @@ where
303304
) -> Result<OccupiedEntry<'_, T, A>, AbsentEntry<'_, T, A>> {
304305
match self.raw.find(hash, eq) {
305306
Some(bucket) => Ok(OccupiedEntry {
306-
hash,
307307
bucket,
308308
table: self,
309309
}),
@@ -413,24 +413,19 @@ where
413413
) -> Entry<'_, T, A> {
414414
match self.raw.find_or_find_insert_slot(hash, eq, hasher) {
415415
Ok(bucket) => Entry::Occupied(OccupiedEntry {
416-
hash,
417416
bucket,
418417
table: self,
419418
}),
420419
Err(insert_slot) => Entry::Vacant(VacantEntry {
421-
hash,
420+
tag: Tag::full(hash),
422421
insert_slot,
423422
table: self,
424423
}),
425424
}
426425
}
427426

428-
/// Returns an `OccupiedEntry` for a bucket index in the table with the given hash,
429-
/// or `None` if the index is out of bounds or if its hash doesn't match.
430-
///
431-
/// However, note that the hash is only compared for the few bits that are directly stored in
432-
/// the table, and even in full this could not guarantee equality. Use [`OccupiedEntry::get`]
433-
/// if you need to further validate a match.
427+
/// Returns an `OccupiedEntry` for the given bucket index in the table,
428+
/// or `None` if it is unoccupied or out of bounds.
434429
///
435430
/// # Examples
436431
///
@@ -447,29 +442,25 @@ where
447442
/// table.insert_unique(hasher(&2), (2, 'b'), |val| hasher(&val.0));
448443
/// table.insert_unique(hasher(&3), (3, 'c'), |val| hasher(&val.0));
449444
///
450-
/// let hash = hasher(&2);
451-
/// let index = table.find_bucket_index(hash, |val| val.0 == 2).unwrap();
445+
/// let index = table.find_bucket_index(hasher(&2), |val| val.0 == 2).unwrap();
452446
///
453-
/// let bad_hash = !hash;
454-
/// assert!(table.get_bucket_entry(bad_hash, index).is_none());
455-
/// assert!(table.get_bucket_entry(hash, usize::MAX).is_none());
447+
/// assert!(table.get_bucket_entry(usize::MAX).is_none());
456448
///
457-
/// let occupied_entry = table.get_bucket_entry(hash, index).unwrap();
449+
/// let occupied_entry = table.get_bucket_entry(index).unwrap();
458450
/// assert_eq!(occupied_entry.get(), &(2, 'b'));
459451
/// assert_eq!(occupied_entry.remove().0, (2, 'b'));
460452
///
461-
/// assert!(table.find(hash, |val| val.0 == 2).is_none());
453+
/// assert!(table.find(hasher(&2), |val| val.0 == 2).is_none());
462454
/// # }
463455
/// # fn main() {
464456
/// # #[cfg(feature = "nightly")]
465457
/// # test()
466458
/// # }
467459
/// ```
468460
#[inline]
469-
pub fn get_bucket_entry(&mut self, hash: u64, index: usize) -> Option<OccupiedEntry<'_, T, A>> {
461+
pub fn get_bucket_entry(&mut self, index: usize) -> Option<OccupiedEntry<'_, T, A>> {
470462
Some(OccupiedEntry {
471-
hash,
472-
bucket: self.raw.checked_bucket(hash, index)?,
463+
bucket: self.raw.checked_bucket(index)?,
473464
table: self,
474465
})
475466
}
@@ -573,7 +564,6 @@ where
573564
) -> OccupiedEntry<'_, T, A> {
574565
let bucket = self.raw.insert(hash, value, hasher);
575566
OccupiedEntry {
576-
hash,
577567
bucket,
578568
table: self,
579569
}
@@ -1771,7 +1761,6 @@ pub struct OccupiedEntry<'a, T, A = Global>
17711761
where
17721762
A: Allocator,
17731763
{
1774-
hash: u64,
17751764
bucket: Bucket<T>,
17761765
table: &'a mut HashTable<T, A>,
17771766
}
@@ -1840,12 +1829,12 @@ where
18401829
/// ```
18411830
#[cfg_attr(feature = "inline-more", inline)]
18421831
pub fn remove(self) -> (T, VacantEntry<'a, T, A>) {
1843-
let (val, slot) = unsafe { self.table.raw.remove(self.bucket) };
1832+
let (val, insert_slot, tag) = unsafe { self.table.raw.remove_tagged(self.bucket) };
18441833
(
18451834
val,
18461835
VacantEntry {
1847-
hash: self.hash,
1848-
insert_slot: slot,
1836+
tag,
1837+
insert_slot,
18491838
table: self.table,
18501839
},
18511840
)
@@ -2083,7 +2072,7 @@ pub struct VacantEntry<'a, T, A = Global>
20832072
where
20842073
A: Allocator,
20852074
{
2086-
hash: u64,
2075+
tag: Tag,
20872076
insert_slot: InsertSlot,
20882077
table: &'a mut HashTable<T, A>,
20892078
}
@@ -2134,10 +2123,9 @@ where
21342123
let bucket = unsafe {
21352124
self.table
21362125
.raw
2137-
.insert_in_slot(self.hash, self.insert_slot, value)
2126+
.insert_tagged_in_slot(self.tag, self.insert_slot, value)
21382127
};
21392128
OccupiedEntry {
2140-
hash: self.hash,
21412129
bucket,
21422130
table: self.table,
21432131
}

0 commit comments

Comments
 (0)