small_map/
inline.rs

1use core::{
2    hash::{BuildHasher, Hash},
3    iter::FusedIterator,
4    mem::{self, transmute, ManuallyDrop, MaybeUninit},
5    ptr::NonNull,
6};
7
8use crate::{
9    raw::{
10        h2,
11        util::{
12            equivalent_key, likely, make_hash, unlikely, Bucket, InsertSlot, SizedTypeProperties,
13        },
14        Group,
15    },
16    Equivalent,
17};
18
19/// Lazy hash computation - computes hash only when needed and caches the result.
20enum HashValue<F> {
21    Pending(F),
22    Computed(u64),
23}
24
25impl<F: FnOnce() -> u64> HashValue<F> {
26    #[inline]
27    fn new(f: F) -> Self {
28        Self::Pending(f)
29    }
30
31    #[inline]
32    fn get(&mut self) -> u64 {
33        match self {
34            Self::Pending(_) => {
35                let f = match core::mem::replace(self, Self::Computed(0)) {
36                    Self::Pending(f) => f,
37                    Self::Computed(_) => unsafe { core::hint::unreachable_unchecked() },
38                };
39                let hash = f();
40                *self = Self::Computed(hash);
41                hash
42            }
43            Self::Computed(hash) => *hash,
44        }
45    }
46}
47
48#[derive(Clone)]
49pub struct Inline<const N: usize, K, V, SH, SI, const LINEAR_THRESHOLD: usize = { Group::WIDTH }> {
50    raw: RawInline<N, (K, V), LINEAR_THRESHOLD>,
51    // Option is for take, S always exists before drop.
52    inline_hasher: Option<SI>,
53    heap_hasher: Option<SH>,
54}
55
56struct RawInline<const N: usize, T, const LINEAR_THRESHOLD: usize = { Group::WIDTH }> {
57    aligned_groups: AlignedGroups<N>,
58    len: usize,
59    data: [MaybeUninit<T>; N],
60}
61
62impl<const N: usize, T: Clone, const LINEAR_THRESHOLD: usize> Clone
63    for RawInline<N, T, LINEAR_THRESHOLD>
64{
65    #[inline]
66    fn clone(&self) -> Self {
67        struct DropGuard<'a, T> {
68            data: &'a mut [MaybeUninit<T>],
69            len: usize,
70        }
71
72        impl<'a, T> Drop for DropGuard<'a, T> {
73            fn drop(&mut self) {
74                // SAFETY: data[0..len] are initialized
75                for i in 0..self.len {
76                    unsafe {
77                        core::ptr::drop_in_place(self.data[i].as_mut_ptr());
78                    }
79                }
80            }
81        }
82
83        // SAFETY: MaybeUninit doesn't require initialization
84        let mut aligned_groups: AlignedGroups<N> = unsafe { MaybeUninit::uninit().assume_init() };
85        let mut data: [MaybeUninit<T>; N] = unsafe { MaybeUninit::uninit().assume_init() };
86
87        let mut guard = DropGuard {
88            data: &mut data,
89            len: 0,
90        };
91
92        // Only 0..len is valid
93        for (i, group) in self.aligned_groups.groups.iter().take(self.len).enumerate() {
94            aligned_groups.groups[i] = *group;
95            // If T::clone panics, guard will drop everything cloned so far
96            guard.data[i] = MaybeUninit::new(unsafe { self.data[i].assume_init_ref().clone() });
97            guard.len += 1;
98        }
99
100        mem::forget(guard);
101
102        Self {
103            aligned_groups,
104            len: self.len,
105            data,
106        }
107    }
108}
109
110#[repr(C)]
111#[derive(Clone, Copy)]
112pub(crate) struct AlignedGroups<const N: usize> {
113    groups: [MaybeUninit<u8>; N],
114    _align: [Group; 0],
115}
116
117impl<const N: usize> AlignedGroups<N> {
118    #[inline]
119    unsafe fn ctrl(&self, index: usize) -> *mut u8 {
120        (self.groups.as_ptr() as *const u8).add(index).cast_mut()
121    }
122}
123
124impl<const N: usize, T, const LINEAR_THRESHOLD: usize> Drop for RawInline<N, T, LINEAR_THRESHOLD> {
125    #[inline]
126    fn drop(&mut self) {
127        unsafe { self.drop_elements() }
128    }
129}
130
131impl<const N: usize, T, const LINEAR_THRESHOLD: usize> RawInline<N, T, LINEAR_THRESHOLD> {
132    /// Minimum len threshold for linear search.
133    /// Always at least 2 to ensure len=1 skips hash computation.
134    const MIN_LINEAR_LEN: usize = if LINEAR_THRESHOLD > 2 {
135        LINEAR_THRESHOLD
136    } else {
137        2
138    };
139
140    #[inline]
141    unsafe fn drop_elements(&mut self) {
142        if T::NEEDS_DROP {
143            // Data is contiguous in 0..len
144            for i in 0..self.len {
145                core::ptr::drop_in_place(self.data[i].as_mut_ptr());
146            }
147        }
148    }
149
150    /// Searches for an element. Uses linear search for small N/len, SIMD otherwise.
151    /// Hash is computed lazily only when SIMD path is taken.
152    #[inline]
153    fn find<F: FnOnce() -> u64>(
154        &self,
155        hash: &mut HashValue<F>,
156        mut eq: impl FnMut(&T) -> bool,
157    ) -> Option<Bucket<T>> {
158        if N < LINEAR_THRESHOLD || self.len < Self::MIN_LINEAR_LEN {
159            // Linear search - no hash needed
160            for i in 0..self.len {
161                if eq(unsafe { self.data.get_unchecked(i).assume_init_ref() }) {
162                    return Some(unsafe { self.bucket(i) });
163                }
164            }
165            None
166        } else {
167            // SIMD search based on len
168            unsafe {
169                let h2_hash = h2(hash.get());
170                let full_groups = self.len / Group::WIDTH;
171                let mut probe_pos = 0;
172
173                for _ in 0..full_groups {
174                    let group = Group::load(self.aligned_groups.ctrl(probe_pos));
175                    for bit in group.match_byte(h2_hash) {
176                        let index = probe_pos + bit;
177                        let item: &T = self.data.get_unchecked(index).assume_init_ref();
178                        if likely(eq(item)) {
179                            return Some(self.bucket(index));
180                        }
181                    }
182                    probe_pos += Group::WIDTH;
183                }
184
185                let tail_len = self.len % Group::WIDTH;
186                if tail_len > 0 {
187                    let group = Group::load(self.aligned_groups.ctrl(probe_pos));
188                    for bit in group.match_byte(h2_hash).and(Group::LOWEST_MASK[tail_len]) {
189                        let index = probe_pos + bit;
190                        let item: &T = self.data.get_unchecked(index).assume_init_ref();
191                        if likely(eq(item)) {
192                            return Some(self.bucket(index));
193                        }
194                    }
195                }
196                None
197            }
198        }
199    }
200
201    /// Inserts a new element into the table in the given slot, and returns its
202    /// raw bucket.
203    #[inline]
204    unsafe fn insert_in_slot(&mut self, hash: u64, slot: InsertSlot, value: T) -> Bucket<T> {
205        // SAFETY: The caller must uphold the safety rules for the [`RawTableInner::set_ctrl_h2`]
206        *self.aligned_groups.ctrl(slot.index) = h2(hash);
207        self.len += 1;
208        let bucket = self.bucket(slot.index);
209        bucket.write(value);
210        bucket
211    }
212
213    /// Finds and removes an element from the table.
214    #[inline]
215    fn remove_entry<F: FnOnce() -> u64>(
216        &mut self,
217        hash: &mut HashValue<F>,
218        eq: impl FnMut(&T) -> bool,
219    ) -> Option<T> {
220        match self.find(hash, eq) {
221            Some(bucket) => Some(unsafe { self.remove(bucket).0 }),
222            None => None,
223        }
224    }
225
226    /// Removes an element from the table, returning it.
227    /// Uses swap-delete: swaps with last element to maintain contiguity.
228    #[inline]
229    #[allow(clippy::needless_pass_by_value)]
230    unsafe fn remove(&mut self, item: Bucket<T>) -> (T, InsertSlot) {
231        let index = self.bucket_index(&item);
232        // Read value before swap-delete
233        let value = item.read();
234        self.erase(index);
235        (value, InsertSlot { index })
236    }
237
238    /// Returns the index of a bucket from a `Bucket`.
239    #[inline]
240    unsafe fn bucket_index(&self, bucket: &Bucket<T>) -> usize {
241        bucket.to_base_index(NonNull::new_unchecked(self.data.as_ptr() as _))
242    }
243
244    /// Erases the element at index using swap-delete.
245    /// Copies the last element to index position to maintain contiguous storage.
246    /// Caller must ensure the value at index has been moved out or dropped.
247    #[inline]
248    unsafe fn erase(&mut self, index: usize) {
249        let last = self.len - 1;
250        if index != last {
251            core::ptr::copy_nonoverlapping(
252                self.data[last].as_ptr(),
253                self.data[index].as_mut_ptr(),
254                1,
255            );
256            self.aligned_groups.groups[index] = self.aligned_groups.groups[last];
257        }
258        self.len -= 1;
259    }
260
261    /// Returns a pointer to an element in the table.
262    #[inline]
263    unsafe fn bucket(&self, index: usize) -> Bucket<T> {
264        Bucket::from_base_index(
265            NonNull::new_unchecked(transmute::<*mut MaybeUninit<T>, *mut T>(
266                self.data.as_ptr().cast_mut(),
267            )),
268            index,
269        )
270    }
271}
272
273impl<const N: usize, K, V, const LINEAR_THRESHOLD: usize> RawInline<N, (K, V), LINEAR_THRESHOLD> {
274    /// Retains only elements where f returns true.
275    /// Uses swap-delete to maintain contiguous storage.
276    #[inline]
277    fn retain<F>(&mut self, f: &mut F)
278    where
279        F: FnMut(&K, &mut V) -> bool,
280    {
281        let mut i = 0;
282        while i < self.len {
283            let (k, v) = unsafe { self.data[i].assume_init_mut() };
284            if f(k, v) {
285                i += 1;
286            } else {
287                unsafe {
288                    core::ptr::drop_in_place(self.data[i].as_mut_ptr());
289                    self.erase(i);
290                }
291                // Don't increment i, check the swapped element
292            }
293        }
294    }
295}
296
297/// Iterator over references to key-value pairs.
298pub struct Iter<'a, const N: usize, K, V> {
299    data: &'a [MaybeUninit<(K, V)>; N],
300    index: usize,
301    len: usize,
302}
303
304impl<'a, const N: usize, K, V> Iterator for Iter<'a, N, K, V> {
305    type Item = (&'a K, &'a V);
306
307    #[inline]
308    fn next(&mut self) -> Option<Self::Item> {
309        if self.index < self.len {
310            let kv = unsafe { self.data[self.index].assume_init_ref() };
311            self.index += 1;
312            Some((&kv.0, &kv.1))
313        } else {
314            None
315        }
316    }
317
318    #[inline]
319    fn size_hint(&self) -> (usize, Option<usize>) {
320        let remaining = self.len - self.index;
321        (remaining, Some(remaining))
322    }
323}
324
325impl<'a, const N: usize, K, V> ExactSizeIterator for Iter<'a, N, K, V> {
326    #[inline]
327    fn len(&self) -> usize {
328        self.len - self.index
329    }
330}
331
332impl<'a, const N: usize, K, V> FusedIterator for Iter<'a, N, K, V> {}
333
334impl<'a, const N: usize, K, V> Clone for Iter<'a, N, K, V> {
335    #[inline]
336    fn clone(&self) -> Self {
337        Self {
338            data: self.data,
339            index: self.index,
340            len: self.len,
341        }
342    }
343}
344
345/// Owning iterator over key-value pairs.
346pub struct IntoIter<const N: usize, K, V> {
347    data: [MaybeUninit<(K, V)>; N],
348    index: usize,
349    len: usize,
350}
351
352impl<const N: usize, K, V> Iterator for IntoIter<N, K, V> {
353    type Item = (K, V);
354
355    #[inline]
356    fn next(&mut self) -> Option<Self::Item> {
357        if self.index < self.len {
358            let kv = unsafe { self.data[self.index].as_ptr().read() };
359            self.index += 1;
360            Some(kv)
361        } else {
362            None
363        }
364    }
365
366    #[inline]
367    fn size_hint(&self) -> (usize, Option<usize>) {
368        let remaining = self.len - self.index;
369        (remaining, Some(remaining))
370    }
371}
372
373impl<const N: usize, K, V> ExactSizeIterator for IntoIter<N, K, V> {
374    #[inline]
375    fn len(&self) -> usize {
376        self.len - self.index
377    }
378}
379
380impl<const N: usize, K, V> FusedIterator for IntoIter<N, K, V> {}
381
382impl<const N: usize, K, V> Drop for IntoIter<N, K, V> {
383    fn drop(&mut self) {
384        // Drop remaining elements
385        for i in self.index..self.len {
386            unsafe { core::ptr::drop_in_place(self.data[i].as_mut_ptr()) };
387        }
388    }
389}
390
391impl<const N: usize, K, V, SH, SI, const LINEAR_THRESHOLD: usize> IntoIterator
392    for Inline<N, K, V, SH, SI, LINEAR_THRESHOLD>
393{
394    type Item = (K, V);
395    type IntoIter = IntoIter<N, K, V>;
396
397    #[inline]
398    fn into_iter(self) -> Self::IntoIter {
399        // Use ManuallyDrop to take ownership of the data while still dropping hashers.
400        let mut this = ManuallyDrop::new(self);
401        let data = unsafe { core::ptr::read(&this.raw.data) };
402        let len = this.raw.len;
403
404        // Drop hashers eagerly to avoid leaking resources when consuming inline storage.
405        unsafe {
406            core::ptr::drop_in_place(&mut this.inline_hasher);
407            core::ptr::drop_in_place(&mut this.heap_hasher);
408        }
409
410        IntoIter {
411            data,
412            index: 0,
413            len,
414        }
415    }
416}
417
418impl<const N: usize, K, V, SH, SI, const LINEAR_THRESHOLD: usize>
419    Inline<N, K, V, SH, SI, LINEAR_THRESHOLD>
420{
421    #[inline]
422    pub(crate) fn iter(&self) -> Iter<'_, N, K, V> {
423        Iter {
424            data: &self.raw.data,
425            index: 0,
426            len: self.raw.len,
427        }
428    }
429
430    // This constant assertion ensures that N is not zero at compile time.
431    // The evaluation of this constant will trigger a compile-time error if N is 0.
432    const VALIDNESS_CHECK: () = assert!(N != 0, "SmallMap cannot be initialized with zero size.");
433
434    #[inline]
435    pub(crate) const fn new(hash_builder: SI, heap_hasher: SH) -> Self {
436        // Trigger compile-time check
437        #[allow(clippy::let_unit_value)]
438        let _ = Self::VALIDNESS_CHECK;
439
440        Self {
441            raw: RawInline {
442                // SAFETY: MaybeUninit doesn't require initialization, len tracks validity
443                aligned_groups: unsafe { MaybeUninit::uninit().assume_init() },
444                len: 0,
445                data: unsafe { MaybeUninit::uninit().assume_init() },
446            },
447            inline_hasher: Some(hash_builder),
448            heap_hasher: Some(heap_hasher),
449        }
450    }
451
452    #[inline]
453    pub(crate) fn is_empty(&self) -> bool {
454        self.raw.len == 0
455    }
456
457    #[inline]
458    pub(crate) fn is_full(&self) -> bool {
459        self.raw.len == N
460    }
461
462    #[inline]
463    pub(crate) fn len(&self) -> usize {
464        self.raw.len
465    }
466
467    // # Safety
468    // Hasher must exist.
469    #[inline]
470    pub(crate) unsafe fn take_inline_hasher(&mut self) -> SI {
471        self.inline_hasher.take().unwrap_unchecked()
472    }
473
474    // # Safety
475    // Hasher must exist.
476    #[inline]
477    pub(crate) unsafe fn take_heap_hasher(&mut self) -> SH {
478        self.heap_hasher.take().unwrap_unchecked()
479    }
480
481    #[inline]
482    fn inline_hasher(&self) -> &SI {
483        self.inline_hasher.as_ref().unwrap()
484    }
485}
486
487impl<const N: usize, K, V, SH, SI, const LINEAR_THRESHOLD: usize>
488    Inline<N, K, V, SH, SI, LINEAR_THRESHOLD>
489where
490    K: Eq + Hash,
491    SI: BuildHasher,
492{
493    /// Returns a reference to the value corresponding to the key.
494    #[inline]
495    pub(crate) fn get<Q>(&self, k: &Q) -> Option<&V>
496    where
497        Q: ?Sized + Hash + Equivalent<K>,
498    {
499        // Avoid `Option::map` because it bloats LLVM IR.
500        match self.get_inner(k) {
501            Some((_, v)) => Some(v),
502            None => None,
503        }
504    }
505
506    /// Returns a reference to the value corresponding to the key.
507    #[inline]
508    pub(crate) fn get_mut<Q>(&mut self, k: &Q) -> Option<&mut V>
509    where
510        Q: ?Sized + Hash + Equivalent<K>,
511    {
512        // Avoid `Option::map` because it bloats LLVM IR.
513        match self.get_inner_mut(k) {
514            Some((_, v)) => Some(v),
515            None => None,
516        }
517    }
518
519    /// Returns the key-value pair corresponding to the supplied key.
520    #[inline]
521    pub(crate) fn get_key_value<Q>(&self, k: &Q) -> Option<(&K, &V)>
522    where
523        Q: ?Sized + Hash + Equivalent<K>,
524    {
525        // Avoid `Option::map` because it bloats LLVM IR.
526        match self.get_inner(k) {
527            Some((key, value)) => Some((key, value)),
528            None => None,
529        }
530    }
531
532    /// Inserts a key-value pair into the map.
533    #[inline]
534    pub(crate) fn insert(&mut self, k: K, v: V) -> Option<V> {
535        if N < LINEAR_THRESHOLD {
536            // Small N: use linear search, no hash needed
537            let len = self.raw.len;
538            for i in 0..len {
539                let item = unsafe { self.raw.data[i].assume_init_mut() };
540                if k.equivalent(&item.0) {
541                    return Some(mem::replace(&mut item.1, v));
542                }
543            }
544            self.raw.data[len].write((k, v));
545            self.raw.len = len + 1;
546            None
547        } else {
548            let hash_builder = self.inline_hasher.as_ref().unwrap();
549            let mut hash = HashValue::new(|| make_hash::<K, SI>(hash_builder, &k));
550            match self.raw.find(&mut hash, equivalent_key(&k)) {
551                Some(bucket) => Some(mem::replace(unsafe { &mut bucket.as_mut().1 }, v)),
552                None => {
553                    let slot = InsertSlot {
554                        index: self.raw.len,
555                    };
556                    unsafe { self.raw.insert_in_slot(hash.get(), slot, (k, v)) };
557                    None
558                }
559            }
560        }
561    }
562
563    /// Inserts a key-value pair into the map without checking if the key already exists.
564    ///
565    /// # Safety
566    /// The caller must ensure that the key does not already exist in the map.
567    #[inline]
568    pub(crate) unsafe fn insert_unique_unchecked(&mut self, k: K, v: V) -> (&K, &mut V) {
569        let len = self.raw.len;
570        if N < LINEAR_THRESHOLD {
571            self.raw.data[len].write((k, v));
572            self.raw.len = len + 1;
573            let item = self.raw.data[len].assume_init_mut();
574            (&item.0, &mut item.1)
575        } else {
576            let hash_builder = self.inline_hasher.as_ref().unwrap();
577            let hash = make_hash::<K, SI>(hash_builder, &k);
578            let slot = InsertSlot { index: len };
579            let bucket = self.raw.insert_in_slot(hash, slot, (k, v));
580            let (k, v) = bucket.as_mut();
581            (k, v)
582        }
583    }
584
585    /// Removes a key from the map, returning the stored key and value if the
586    /// key was previously in the map. Keeps the allocated memory for reuse.
587    #[inline]
588    pub(crate) fn remove_entry<Q>(&mut self, k: &Q) -> Option<(K, V)>
589    where
590        Q: ?Sized + Hash + Equivalent<K>,
591    {
592        let hash_builder = self.inline_hasher.as_ref().unwrap();
593        let mut hash = HashValue::new(|| make_hash::<Q, SI>(hash_builder, k));
594        self.raw.remove_entry(&mut hash, equivalent_key(k))
595    }
596
597    /// Retains only the elements specified by the predicate.
598    #[inline]
599    pub(crate) fn retain<F>(&mut self, f: &mut F)
600    where
601        F: FnMut(&K, &mut V) -> bool,
602    {
603        self.raw.retain(f);
604    }
605
606    #[inline]
607    fn get_inner<Q>(&self, k: &Q) -> Option<&(K, V)>
608    where
609        Q: ?Sized + Hash + Equivalent<K>,
610    {
611        if unlikely(self.is_empty()) {
612            return None;
613        }
614        let mut hash = HashValue::new(|| make_hash::<Q, SI>(self.inline_hasher(), k));
615        match self.raw.find(&mut hash, equivalent_key(k)) {
616            Some(bucket) => Some(unsafe { bucket.as_ref() }),
617            None => None,
618        }
619    }
620
621    #[inline]
622    fn get_inner_mut<Q>(&mut self, k: &Q) -> Option<&mut (K, V)>
623    where
624        Q: ?Sized + Hash + Equivalent<K>,
625    {
626        if unlikely(self.is_empty()) {
627            return None;
628        }
629        let hash_builder = self.inline_hasher.as_ref().unwrap();
630        let mut hash = HashValue::new(|| make_hash::<Q, SI>(hash_builder, k));
631        match self.raw.find(&mut hash, equivalent_key(k)) {
632            Some(bucket) => Some(unsafe { bucket.as_mut() }),
633            None => None,
634        }
635    }
636}