Skip to content

Commit 874f6c9

Browse files
committed
Doc RawTableInner::find_insert_slot and also make it unsafe
1 parent f238820 commit 874f6c9

File tree

1 file changed

+50
-3
lines changed

1 file changed

+50
-3
lines changed

src/raw/mod.rs

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1586,16 +1586,46 @@ impl<A: Allocator + Clone> RawTableInner<A> {
15861586
}
15871587

15881588
/// Searches for an empty or deleted bucket which is suitable for inserting
1589-
/// a new element.
1589+
/// a new element, returning the `index` for the new [`Bucket`].
15901590
///
1591-
/// There must be at least 1 empty bucket in the table.
1591+
/// The table must have at least 1 empty or deleted `bucket`, otherwise this function
1592+
/// will never return (will go into an infinite loop) for tables larger than the group
1593+
/// width, or return an index outside of the table indices range if the table is less
1594+
/// than the group width.
1595+
///
1596+
/// # Safety
1597+
///
1598+
/// Actually, calling this function is always safe, but attempting to write data at
1599+
/// the index returned by this function when the table is less than the group width
1600+
/// and if there was not at least one empty bucket in the table will cause immediate
1601+
/// [`undefined behavior`].
1602+
///
1603+
/// This is because in this case the function will return `self.bucket_mask + 1`
1604+
/// as an index due to the trailing EMPTY control bytes outside the table range.
1605+
///
1606+
/// [`undefined behavior`]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
15921607
#[inline]
1593-
fn find_insert_slot(&self, hash: u64) -> usize {
1608+
unsafe fn find_insert_slot(&self, hash: u64) -> usize {
15941609
let mut probe_seq = self.probe_seq(hash);
15951610
loop {
1611+
// SAFETY:
1612+
// * `ProbeSeq.pos` cannot be greater than `self.bucket_mask = self.buckets() - 1`
1613+
// of the table due to masking with `self.bucket_mask` and also because mumber of
1614+
// buckets is a power of two (see comment for masking below).
1615+
//
1616+
// * Even if `ProbeSeq.pos` returns `position == self.bucket_mask`, it is safe to
1617+
// call `Group::load` due to the extended control bytes range, which is
1618+
// `self.bucket_mask + 1 + Group::WIDTH` (in fact, this means that the last control
1619+
// byte will never be read for the allocated table);
1620+
//
1621+
// * Also, even if `RawTableInner` is not already allocated, `ProbeSeq.pos` will
1622+
// always return "0" (zero), so Group::load will read unaligned `Group::static_empty()`
1623+
// bytes, which is safe (see RawTableInner::new_in).
15961624
unsafe {
15971625
let group = Group::load(self.ctrl(probe_seq.pos));
15981626
if let Some(bit) = group.match_empty_or_deleted().lowest_set_bit() {
1627+
// This is the same as `(probe_seq.pos + bit) % self.buckets()` because the number
1628+
// of buckets is a power of two, and `self.bucket_mask = self.buckets() - 1`.
15991629
let result = (probe_seq.pos + bit) & self.bucket_mask;
16001630

16011631
// In tables smaller than the group width, trailing control
@@ -1607,9 +1637,26 @@ impl<A: Allocator + Clone> RawTableInner<A> {
16071637
// table. This second scan is guaranteed to find an empty
16081638
// slot (due to the load factor) before hitting the trailing
16091639
// control bytes (containing EMPTY).
1640+
//
1641+
// SAFETY: The `result` is guaranteed to be in range `0..self.bucket_mask`
1642+
// due to masking with `self.bucket_mask`
16101643
if unlikely(self.is_bucket_full(result)) {
16111644
debug_assert!(self.bucket_mask < Group::WIDTH);
16121645
debug_assert_ne!(probe_seq.pos, 0);
1646+
// SAFETY:
1647+
//
1648+
// * We are in range and `ptr = self.ctrl(0)` are valid for reads
1649+
// and properly aligned, because the table is already allocated
1650+
// (see `TableLayout::calculate_layout_for` and `ptr::read`);
1651+
//
1652+
// * For tables larger than the group width, we will never end up in the given
1653+
// branch, since `(probe_seq.pos + bit) & self.bucket_mask` cannot return a
1654+
// full bucket index. For tables smaller than the group width, calling the
1655+
// `lowest_set_bit_nonzero` function (when `nightly` feature enabled) is also
1656+
// safe, as the trailing control bytes outside the range of the table are filled
1657+
// with EMPTY bytes, so this second scan either finds an empty slot (due to the
1658+
// load factor) or hits the trailing control bytes (containing EMPTY). See
1659+
// `intrinsics::cttz_nonzero` for more information.
16131660
return Group::load_aligned(self.ctrl(0))
16141661
.match_empty_or_deleted()
16151662
.lowest_set_bit_nonzero();

0 commit comments

Comments
 (0)