Skip to main content

musli_zerocopy/swiss/
constructor.rs

1// Note: This was ported from `hashbrown`, and still contains some of the
2// comments assuming that it performs internal allocations. These are most
3// likely wrong and need to be rewritten to take into account the safety
4// requirements towards `OwnedBuf`.
5
6use core::convert::{identity as likely, identity as unlikely};
7use core::marker::PhantomData;
8use core::mem::size_of;
9use core::ptr::NonNull;
10
11use crate::buf::{self, Buf, StoreBuf};
12use crate::error::{Error, ErrorKind};
13use crate::swiss::raw::{Group, ProbeSeq, h2, is_full, probe_seq, special_is_empty};
14use crate::traits::ZeroCopy;
15
16/// Construction of a raw swiss table.
17pub struct Constructor<'a, T, S: ?Sized> {
18    buf: &'a mut S,
19
20    // Mask to get an index from a hash value. The value is one less than the
21    // number of buckets in the table.
22    bucket_mask: usize,
23
24    // Control offset, where the control vectors are stored in `buf`.
25    ctrl_ptr: usize,
26
27    // Base offset where items are written in `buf`.
28    base_ptr: usize,
29
30    // Number of elements that can be inserted before we need to grow the table.
31    // Since we can't grow the table, reaching this point results in an error.
32    growth_left: usize,
33
34    // Hold onto T to make sure the API stays coherent with the types the
35    // constructor can write.
36    _marker: PhantomData<T>,
37}
38
39impl<'a, T, S: ?Sized> Constructor<'a, T, S>
40where
41    S: StoreBuf,
42{
43    /// Wrap the given buffer for table construction.
44    ///
45    /// # Safety
46    ///
47    /// The caller must ensure that buffer contains allocated and correctly
48    /// initialized memory at `ctrl_ptr` and `base_ptr`.
49    ///
50    /// * `ctrl_ptr` must point to a memory region that is `buckets + 1` length
51    ///   sized for `Group` which has been bitwise initialized to [`EMPTY`].
52    /// * `base_ptr` must point to a memory region that is `buckets` length
53    ///   sized for `T`.
54    /// * `buckets` must be a power of two.
55    ///
56    /// [`EMPTY`]: crate::swiss::raw::EMPTY
57    pub(crate) fn with_buf(
58        buf: &'a mut S,
59        ctrl_ptr: usize,
60        base_ptr: usize,
61        buckets: usize,
62    ) -> Self {
63        debug_assert!(buckets.is_power_of_two());
64
65        Self {
66            buf,
67            bucket_mask: buckets - 1,
68            ctrl_ptr,
69            base_ptr,
70            growth_left: bucket_mask_to_capacity(buckets - 1),
71            _marker: PhantomData,
72        }
73    }
74
75    /// Access the underlying buffer.
76    pub(crate) fn buf(&self) -> &Buf {
77        self.buf.as_buf()
78    }
79
80    /// Export bucket mask.
81    pub(crate) fn bucket_mask(&self) -> usize {
82        self.bucket_mask
83    }
84
85    /// Get the length of the table.
86    pub(crate) fn len(&self) -> usize {
87        // NB: `growth_left` starts out at the effective capacity of the table
88        // rather than at the number of buckets, since a portion of the buckets
89        // is reserved to always stay empty.
90        bucket_mask_to_capacity(self.bucket_mask) - self.growth_left
91    }
92
93    /// Returns the number of buckets in the table.
94    #[inline]
95    pub(crate) fn buckets(&self) -> usize {
96        self.bucket_mask + 1
97    }
98
99    /// Insert the given zero copy value into the table.
100    pub(crate) fn insert(&mut self, hash: u64, value: &T) -> Result<Bucket<'_, T>, Error>
101    where
102        T: ZeroCopy,
103    {
104        // SAFETY:
105        // 1. The [`Constructor`] must already have properly initialized control bytes since
106        //    we will never expose `Constructor::new_uninitialized` in a public API.
107        let slot = self.find_insert_slot(hash)?;
108
109        // We can avoid growing the table once we have reached our load factor if we are replacing
110        // a tombstone. This works since the number of EMPTY slots does not change in this case.
111        //
112        // SAFETY: The function is guaranteed to return [`InsertSlot`] that contains an index
113        // in the range `0..=self.buckets()`.
114        let old_ctrl = *self.ctrl(slot.index);
115
116        if unlikely(self.growth_left == 0 && special_is_empty(old_ctrl)) {
117            return Err(Error::new(ErrorKind::CapacityError));
118        }
119
120        Ok(self.insert_in_slot(hash, slot, value))
121    }
122
123    /// Inserts a new element into the table in the given slot, and returns its
124    /// raw bucket.
125    #[inline]
126    pub fn insert_in_slot(&mut self, hash: u64, slot: InsertSlot, value: &T) -> Bucket<'_, T>
127    where
128        T: ZeroCopy,
129    {
130        let old_ctrl = *self.ctrl(slot.index);
131        self.record_item_insert_at(slot.index, old_ctrl, hash);
132        let bucket = self.bucket(slot.index);
133        bucket.write(value);
134        bucket
135    }
136
137    /// Returns a pointer to an element in the table.
138    #[inline]
139    pub fn bucket(&mut self, index: usize) -> Bucket<'_, T> {
140        debug_assert_ne!(self.bucket_mask, 0);
141        debug_assert!(index < self.buckets());
142
143        let Some(index) = index.checked_mul(size_of::<T>()) else {
144            panic!("Index `{index}` out of bounds");
145        };
146
147        let Some(start) = self.base_ptr.checked_add(index) else {
148            panic!("Start `{index}` out of bounds");
149        };
150
151        let end = start + size_of::<T>();
152
153        // SAFETY: The accessed data is a slice of u8's, which cannot be
154        // incorrectly initialized locally.
155        let slice = unsafe { self.buf.get_mut(start..end) };
156
157        let Some(slice) = slice else {
158            panic!("Missing bucket at range {start}..{end}");
159        };
160
161        // SAFETY: We've ensure that the bucket is appropriately sized just
162        // above.
163        unsafe { Bucket::from_slice(slice) }
164    }
165
166    /// Finds the position to insert something in a group.
167    ///
168    /// **This may have false positives and must be fixed up with `fix_insert_slot`
169    /// before it's used.**
170    ///
171    /// The function is guaranteed to return the index of an empty or deleted [`Bucket`]
172    /// in the range `0..self.buckets()` (`0..=self.bucket_mask`).
173    #[inline]
174    fn find_insert_slot_in_group(&self, group: &Group, probe_seq: &ProbeSeq) -> Option<usize> {
175        let bit = likely(group.match_empty_or_deleted().lowest_set_bit()?);
176
177        // This is the same as `(probe_seq.pos + bit) % self.buckets()` because the number
178        // of buckets is a power of two, and `self.bucket_mask = self.buckets() - 1`.
179        Some((probe_seq.pos + bit) & self.bucket_mask)
180    }
181
182    #[inline]
183    fn record_item_insert_at(&mut self, index: usize, old_ctrl: u8, hash: u64) {
184        self.growth_left -= usize::from(special_is_empty(old_ctrl));
185        self.set_ctrl_h2(index, hash);
186    }
187
188    /// Searches for an empty or deleted bucket which is suitable for inserting
189    /// a new element, returning the `index` for the new [`Bucket`].
190    #[inline]
191    fn find_insert_slot(&mut self, hash: u64) -> Result<InsertSlot, Error> {
192        let mut probe_seq = probe_seq(self.bucket_mask, hash);
193
194        loop {
195            let group = unsafe { Group::load(self.ctrl_group(probe_seq.pos).as_ptr()) };
196
197            if let Some(index) = self.find_insert_slot_in_group(&group, &probe_seq) {
198                return Ok(self.fix_insert_slot(index));
199            };
200
201            probe_seq.move_next(self.bucket_mask)?;
202        }
203    }
204
205    /// Fixes up an insertion slot returned by the
206    /// [`Constructor::find_insert_slot_in_group`] method.
207    ///
208    /// In tables smaller than the group width (`self.buckets() <
209    /// Group::WIDTH`), trailing control bytes outside the range of the table
210    /// are filled with [`EMPTY`] entries. These will unfortunately trigger a
211    /// match of [`Constructor::find_insert_slot_in_group`] function. This is
212    /// because the `Some(bit)` returned by
213    /// `group.match_empty_or_deleted().lowest_set_bit()` after masking
214    /// (`(probe_seq.pos + bit) & self.bucket_mask`) may point to a full bucket
215    /// that is already occupied. We detect this situation here and perform a
216    /// second scan starting at the beginning of the table. This second scan is
217    /// guaranteed to find an empty slot (due to the load factor) before hitting
218    /// the trailing control bytes (containing [`EMPTY`] bytes).
219    ///
220    /// If this function is called correctly, it is guaranteed to return
221    /// [`InsertSlot`] with an index of an empty or deleted bucket in the range
222    /// `0..self.buckets()` (see `Warning` and `Safety`).
223    ///
224    /// [`EMPTY`]: super::raw::EMPTY
225    ///
226    /// # Warning
227    ///
228    /// The table must have at least 1 empty or deleted `bucket`, otherwise if
229    /// the table is less than the group width (`self.buckets() < Group::WIDTH`)
230    /// this function returns an index outside of the table indices range
231    /// `0..self.buckets()` (`0..=self.bucket_mask`). Attempt to write data at
232    /// that index will cause immediate [`undefined behavior`].
233    ///
234    /// # Safety
235    ///
236    /// The safety rules are directly derived from the safety rules for
237    /// [`Constructor::ctrl`] method. Thus, in order to uphold those safety
238    /// contracts, as well as for the correct logic of the work of this crate,
239    /// the following rules are necessary and sufficient:
240    ///
241    /// * The [`Constructor`] must have properly initialized control bytes
242    ///   otherwise calling this function results in [`undefined behavior`].
243    ///
244    /// * This function must only be used on insertion slots found by
245    ///   [`find_insert_slot_in_group`] (after the `find_insert_slot_in_group`
246    ///   function, but before insertion into the table).
247    ///
248    /// * The `index` must not be greater than the `self.bucket_mask`, i.e.
249    ///   `(index + 1) <= self.buckets()` (this one is provided by the
250    ///   [`find_insert_slot_in_group`] function).
251    ///
252    /// Calling this function with an index not provided by
253    /// [`find_insert_slot_in_group`] may result in [`undefined behavior`] even
254    /// if the index satisfies the safety rules of the [`ctrl`] function (`index
255    /// < self.bucket_mask + 1 + Group::WIDTH`).
256    ///
257    /// [`ctrl`]: Self::ctrl
258    /// [`find_insert_slot_in_group`]: Self::find_insert_slot_in_group
259    /// [`undefined behavior`]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
260    #[inline]
261    fn fix_insert_slot(&mut self, mut index: usize) -> InsertSlot {
262        // SAFETY: The caller of this function ensures that `index` is in the range `0..=self.bucket_mask`.
263        if unlikely(self.is_bucket_full(index)) {
264            debug_assert!(self.bucket_mask < Group::WIDTH);
265
266            // SAFETY:
267            //
268            // * Since the caller of this function ensures that the control bytes are properly
269            //   initialized and `ptr = self.ctrl(0)` points to the start of the array of control
270            //   bytes, therefore: `ctrl` is valid for reads, properly aligned to `Group::WIDTH`
271            //   and points to the properly initialized control bytes (see also
272            //   `TableLayout::calculate_layout_for` and `ptr::read`);
273            //
274            // * Because the caller of this function ensures that the index was provided by the
275            //   `self.find_insert_slot_in_group()` function, so for for tables larger than the
276            //   group width (self.buckets() >= Group::WIDTH), we will never end up in the given
277            //   branch, since `(probe_seq.pos + bit) & self.bucket_mask` in `find_insert_slot_in_group`
278            //   cannot return a full bucket index. For tables smaller than the group width, calling
279            //   the `unwrap_unchecked` function is also safe, as the trailing control bytes outside
280            //   the range of the table are filled with EMPTY bytes (and we know for sure that there
281            //   is at least one FULL bucket), so this second scan either finds an empty slot (due to
282            //   the load factor) or hits the trailing control bytes (containing EMPTY).
283            index = unsafe {
284                Group::load_aligned(self.ctrl_group(0).as_ptr())
285                    .match_empty_or_deleted()
286                    .lowest_set_bit()
287                    .unwrap_unchecked()
288            };
289        }
290
291        InsertSlot { index }
292    }
293
294    /// Sets a control byte to the hash, and possibly also the replicated control byte at
295    /// the end of the array.
296    ///
297    /// This function does not make any changes to the `data` parts of the table,
298    /// or any changes to the the `items` or `growth_left` field of the table.
299    ///
300    /// # Safety
301    ///
302    /// The caller must ensure that the `index` is not out of bounds of the control allocation.
303    #[inline]
304    fn set_ctrl_h2(&mut self, index: usize, hash: u64) {
305        self.set_ctrl(index, h2(hash));
306    }
307
308    /// Sets a control byte, and possibly also the replicated control byte at
309    /// the end of the array.
310    ///
311    /// This function does not make any changes to the `data` parts of the table,
312    /// or any changes to the the `items` or `growth_left` field of the table.
313    ///
314    /// # Safety
315    ///
316    /// The caller must ensure that `index` is not out of bounds of the control
317    /// allocation.
318    #[inline]
319    fn set_ctrl(&mut self, index: usize, ctrl: u8) {
320        // Replicate the first Group::WIDTH control bytes at the end of
321        // the array without using a branch. If the tables smaller than
322        // the group width (self.buckets() < Group::WIDTH),
323        // `index2 = Group::WIDTH + index`, otherwise `index2` is:
324        //
325        // - If index >= Group::WIDTH then index == index2.
326        // - Otherwise index2 == self.bucket_mask + 1 + index.
327        //
328        // The very last replicated control byte is never actually read because
329        // we mask the initial index for unaligned loads, but we write it
330        // anyways because it makes the set_ctrl implementation simpler.
331        //
332        // If there are fewer buckets than Group::WIDTH then this code will
333        // replicate the buckets at the end of the trailing group. For example
334        // with 2 buckets and a group size of 4, the control bytes will look
335        // like this:
336        //
337        //     Real    |             Replicated
338        // ---------------------------------------------
339        // | [A] | [B] | [EMPTY] | [EMPTY] | [A] | [B] |
340        // ---------------------------------------------
341
342        // This is the same as `(index.wrapping_sub(Group::WIDTH)) % self.buckets() + Group::WIDTH`
343        // because the number of buckets is a power of two, and `self.bucket_mask = self.buckets() - 1`.
344        let index2 = ((index.wrapping_sub(Group::WIDTH)) & self.bucket_mask) + Group::WIDTH;
345
346        // SAFETY: The caller must uphold the safety rules for the
347        // `Constructor::set_ctrl`.
348        *self.ctrl_mut(index) = ctrl;
349        *self.ctrl_mut(index2) = ctrl;
350    }
351
352    /// Checks whether the bucket at `index` is full.
353    ///
354    /// # Safety
355    ///
356    /// The caller must ensure `index` is less than the number of buckets.
357    #[inline]
358    fn is_bucket_full(&self, index: usize) -> bool {
359        debug_assert!(index < self.buckets());
360        is_full(*self.ctrl(index))
361    }
362
363    #[inline]
364    fn ctrl_mut(&mut self, index: usize) -> &mut u8 {
365        debug_assert!(index < self.num_ctrl_bytes());
366
367        let offset = self.ctrl_ptr + index;
368
369        // SAFETY: The control bytes are u8's, which cannot be incorrectly
370        // initialized.
371        let ctrl = unsafe { self.buf.get_mut(offset) };
372
373        let Some(ctrl) = ctrl else {
374            panic!("Missing control byte at {offset}");
375        };
376
377        ctrl
378    }
379
380    #[inline]
381    fn ctrl(&self, index: usize) -> &u8 {
382        debug_assert!(index < self.num_ctrl_bytes());
383
384        let offset = self.ctrl_ptr + index;
385
386        let Some(ctrl) = self.buf.get(offset) else {
387            panic!("Missing control byte at {offset}");
388        };
389
390        ctrl
391    }
392
393    #[inline]
394    fn ctrl_group(&self, index: usize) -> &[u8] {
395        debug_assert!(index < self.num_ctrl_bytes());
396
397        let start = self.ctrl_ptr + index;
398        let end = start + Group::WIDTH;
399
400        let Some(ctrl) = self.buf.get(start..end) else {
401            panic!("Missing control byte at range {start}..{end}");
402        };
403
404        ctrl
405    }
406
407    #[inline]
408    fn num_ctrl_bytes(&self) -> usize {
409        self.bucket_mask + 1 + Group::WIDTH
410    }
411}
412
413/// A reference to a hash table bucket containing a `T`.
414///
415/// This is usually just a pointer to the element itself. However if the element
416/// is a ZST, then we instead track the index of the element in the table so
417/// that `erase` works properly.
418pub struct Bucket<'a, T> {
419    data: NonNull<u8>,
420    _marker: PhantomData<&'a mut T>,
421}
422
423impl<'a, T> Bucket<'a, T> {
424    /// Construct a bucket from the given slice.
425    ///
426    /// # Safety
427    ///
428    /// Caller must ensure that the slice is sized for `T`.
429    #[inline]
430    unsafe fn from_slice(data: &'a mut [u8]) -> Self {
431        debug_assert!(data.len() == size_of::<T>());
432
433        Self {
434            data: unsafe { NonNull::new_unchecked(data.as_mut_ptr()) },
435            _marker: PhantomData,
436        }
437    }
438
439    /// Overwrites a memory location with the given `value` without reading or
440    /// dropping the old value (like [`ptr::write`] function).
441    ///
442    /// [`ptr::write`]: core::ptr::write
443    #[inline]
444    pub(crate) fn write(&self, value: &T)
445    where
446        T: ZeroCopy,
447    {
448        // SAFETY: During bucket construction we've asserted that a buffer of
449        // the appropriate size (that is not guaranteed to be aligned) is used.
450        unsafe { buf::store_unaligned(self.data.cast(), value) }
451    }
452}
453
454/// A reference to an empty bucket into which an can be inserted.
455pub struct InsertSlot {
456    index: usize,
457}
458
459/// Returns the maximum effective capacity for the given bucket mask, taking
460/// the maximum load factor into account.
461#[inline]
462fn bucket_mask_to_capacity(bucket_mask: usize) -> usize {
463    if bucket_mask < 8 {
464        // For tables with 1/2/4/8 buckets, we always reserve one empty slot.
465        // Keep in mind that the bucket mask is one less than the bucket count.
466        bucket_mask
467    } else {
468        // For larger tables we reserve 12.5% of the slots as empty.
469        ((bucket_mask + 1) / 8) * 7
470    }
471}