Skip to content

Commit 32d1dac

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 32d1dac

File tree

2 files changed

+57
-37
lines changed

2 files changed

+57
-37
lines changed

src/raw/mod.rs

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::alloc::alloc::{handle_alloc_error, Layout};
2-
use crate::control::{BitMaskIter, Group, Tag, TagSliceExt};
2+
use crate::control::{BitMaskIter, Group, TagSliceExt};
33
use crate::scopeguard::{guard, ScopeGuard};
44
use crate::util::{invalid_mut, likely, unlikely};
55
use crate::TryReserveError;
@@ -11,6 +11,8 @@ use core::ptr::NonNull;
1111
use core::slice;
1212
use core::{hint, ptr};
1313

14+
pub(crate) use crate::control::Tag;
15+
1416
mod alloc;
1517
#[cfg(test)]
1618
pub(crate) use self::alloc::AllocError;
@@ -848,6 +850,19 @@ impl<T, A: Allocator> RawTable<T, A> {
848850
)
849851
}
850852

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

1186-
/// Inserts a new element into the table in the given slot, and returns its
1187-
/// raw bucket.
1201+
/// Inserts a new element into the table in the given slot with the given hash,
1202+
/// and returns its raw bucket.
11881203
///
11891204
/// # Safety
11901205
///
@@ -1193,8 +1208,26 @@ impl<T, A: Allocator> RawTable<T, A> {
11931208
/// occurred since that call.
11941209
#[inline]
11951210
pub unsafe fn insert_in_slot(&mut self, hash: u64, slot: InsertSlot, value: T) -> Bucket<T> {
1211+
self.insert_tagged_in_slot(Tag::full(hash), slot, value)
1212+
}
1213+
1214+
/// Inserts a new element into the table in the given slot with the given tag,
1215+
/// and returns its raw bucket.
1216+
///
1217+
/// # Safety
1218+
///
1219+
/// `slot` must point to a slot previously returned by
1220+
/// `find_or_find_insert_slot`, and no mutation of the table must have
1221+
/// occurred since that call.
1222+
#[inline]
1223+
pub unsafe fn insert_tagged_in_slot(
1224+
&mut self,
1225+
tag: Tag,
1226+
slot: InsertSlot,
1227+
value: T,
1228+
) -> Bucket<T> {
11961229
let old_ctrl = *self.table.ctrl(slot.index);
1197-
self.table.record_item_insert_at(slot.index, old_ctrl, hash);
1230+
self.table.record_item_insert_at(slot.index, old_ctrl, tag);
11981231

11991232
let bucket = self.bucket(slot.index);
12001233
bucket.write(value);
@@ -1269,11 +1302,11 @@ impl<T, A: Allocator> RawTable<T, A> {
12691302
}
12701303

12711304
/// 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.
1305+
/// the index is in-bounds and the bucket is occupied.
12731306
#[inline]
1274-
pub fn checked_bucket(&self, hash: u64, index: usize) -> Option<Bucket<T>> {
1307+
pub fn checked_bucket(&self, index: usize) -> Option<Bucket<T>> {
12751308
unsafe {
1276-
if index < self.buckets() && *self.table.ctrl(index) == Tag::full(hash) {
1309+
if index < self.buckets() && self.is_bucket_full(index) {
12771310
Some(self.bucket(index))
12781311
} else {
12791312
None
@@ -2456,9 +2489,9 @@ impl RawTableInner {
24562489
}
24572490

24582491
#[inline]
2459-
unsafe fn record_item_insert_at(&mut self, index: usize, old_ctrl: Tag, hash: u64) {
2492+
unsafe fn record_item_insert_at(&mut self, index: usize, old_ctrl: Tag, new_ctrl: Tag) {
24602493
self.growth_left -= usize::from(old_ctrl.special_is_empty());
2461-
self.set_ctrl_hash(index, hash);
2494+
self.set_ctrl(index, new_ctrl);
24622495
self.items += 1;
24632496
}
24642497

src/table.rs

Lines changed: 15 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use core::{fmt, iter::FusedIterator, marker::PhantomData};
33
use crate::{
44
raw::{
55
Allocator, Bucket, Global, InsertSlot, RawDrain, RawExtractIf, RawIntoIter, RawIter,
6-
RawIterHash, RawTable,
6+
RawIterHash, RawTable, Tag,
77
},
88
TryReserveError,
99
};
@@ -303,7 +303,6 @@ where
303303
) -> Result<OccupiedEntry<'_, T, A>, AbsentEntry<'_, T, A>> {
304304
match self.raw.find(hash, eq) {
305305
Some(bucket) => Ok(OccupiedEntry {
306-
hash,
307306
bucket,
308307
table: self,
309308
}),
@@ -413,24 +412,19 @@ where
413412
) -> Entry<'_, T, A> {
414413
match self.raw.find_or_find_insert_slot(hash, eq, hasher) {
415414
Ok(bucket) => Entry::Occupied(OccupiedEntry {
416-
hash,
417415
bucket,
418416
table: self,
419417
}),
420418
Err(insert_slot) => Entry::Vacant(VacantEntry {
421-
hash,
419+
tag: Tag::full(hash),
422420
insert_slot,
423421
table: self,
424422
}),
425423
}
426424
}
427425

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.
426+
/// Returns an `OccupiedEntry` for the given bucket index in the table,
427+
/// or `None` if it is unoccupied or out of bounds.
434428
///
435429
/// # Examples
436430
///
@@ -447,29 +441,25 @@ where
447441
/// table.insert_unique(hasher(&2), (2, 'b'), |val| hasher(&val.0));
448442
/// table.insert_unique(hasher(&3), (3, 'c'), |val| hasher(&val.0));
449443
///
450-
/// let hash = hasher(&2);
451-
/// let index = table.find_bucket_index(hash, |val| val.0 == 2).unwrap();
444+
/// let index = table.find_bucket_index(hasher(&2), |val| val.0 == 2).unwrap();
452445
///
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());
446+
/// assert!(table.get_bucket_entry(usize::MAX).is_none());
456447
///
457-
/// let occupied_entry = table.get_bucket_entry(hash, index).unwrap();
448+
/// let occupied_entry = table.get_bucket_entry(index).unwrap();
458449
/// assert_eq!(occupied_entry.get(), &(2, 'b'));
459450
/// assert_eq!(occupied_entry.remove().0, (2, 'b'));
460451
///
461-
/// assert!(table.find(hash, |val| val.0 == 2).is_none());
452+
/// assert!(table.find(hasher(&2), |val| val.0 == 2).is_none());
462453
/// # }
463454
/// # fn main() {
464455
/// # #[cfg(feature = "nightly")]
465456
/// # test()
466457
/// # }
467458
/// ```
468459
#[inline]
469-
pub fn get_bucket_entry(&mut self, hash: u64, index: usize) -> Option<OccupiedEntry<'_, T, A>> {
460+
pub fn get_bucket_entry(&mut self, index: usize) -> Option<OccupiedEntry<'_, T, A>> {
470461
Some(OccupiedEntry {
471-
hash,
472-
bucket: self.raw.checked_bucket(hash, index)?,
462+
bucket: self.raw.checked_bucket(index)?,
473463
table: self,
474464
})
475465
}
@@ -573,7 +563,6 @@ where
573563
) -> OccupiedEntry<'_, T, A> {
574564
let bucket = self.raw.insert(hash, value, hasher);
575565
OccupiedEntry {
576-
hash,
577566
bucket,
578567
table: self,
579568
}
@@ -1771,7 +1760,6 @@ pub struct OccupiedEntry<'a, T, A = Global>
17711760
where
17721761
A: Allocator,
17731762
{
1774-
hash: u64,
17751763
bucket: Bucket<T>,
17761764
table: &'a mut HashTable<T, A>,
17771765
}
@@ -1840,12 +1828,12 @@ where
18401828
/// ```
18411829
#[cfg_attr(feature = "inline-more", inline)]
18421830
pub fn remove(self) -> (T, VacantEntry<'a, T, A>) {
1843-
let (val, slot) = unsafe { self.table.raw.remove(self.bucket) };
1831+
let (val, insert_slot, tag) = unsafe { self.table.raw.remove_tagged(self.bucket) };
18441832
(
18451833
val,
18461834
VacantEntry {
1847-
hash: self.hash,
1848-
insert_slot: slot,
1835+
tag,
1836+
insert_slot,
18491837
table: self.table,
18501838
},
18511839
)
@@ -2083,7 +2071,7 @@ pub struct VacantEntry<'a, T, A = Global>
20832071
where
20842072
A: Allocator,
20852073
{
2086-
hash: u64,
2074+
tag: Tag,
20872075
insert_slot: InsertSlot,
20882076
table: &'a mut HashTable<T, A>,
20892077
}
@@ -2134,10 +2122,9 @@ where
21342122
let bucket = unsafe {
21352123
self.table
21362124
.raw
2137-
.insert_in_slot(self.hash, self.insert_slot, value)
2125+
.insert_tagged_in_slot(self.tag, self.insert_slot, value)
21382126
};
21392127
OccupiedEntry {
2140-
hash: self.hash,
21412128
bucket,
21422129
table: self.table,
21432130
}

0 commit comments

Comments
 (0)