Skip to main content

kovan_map/hopscotch/
iter.rs

1//! Walking the map: a borrowed walk that keeps the table it started on and yields a key once
2//! (it recognizes a key a displacement carried ahead of it), and the owned walk that consumes
3//! the map.
4
5extern crate alloc;
6
7use super::table::{Entry, Table, Walk, Word};
8use super::{HopscotchMap, NEIGHBORHOOD_SIZE};
9use alloc::boxed::Box;
10use core::hash::{BuildHasher, Hash};
11use core::marker::PhantomData;
12use core::sync::atomic::Ordering;
13use kovan::pin;
14
15// Construction: none of `iter`/`keys`/`values` hashes or clones a value (the walk that does
16// lives in `Iterator for HopscotchIter` below). `K: Eq` is captured here as the walk's key
17// comparison (how it recognizes a key it already met), so the `Iterator` impls keep the bounds
18// they have always had, `K: Clone` and `V: Clone`.
19impl<K: Eq + 'static, V: 'static, S> HopscotchMap<K, V, S> {
20    /// Returns an iterator over the map entries.
21    ///
22    /// The iterator walks the table that is current when it is created, to
23    /// its end, even when a resize replaces that table meanwhile (the walk's
24    /// position means nothing in another table, whose layout differs). An
25    /// entry present from the iterator's creation to its end is yielded
26    /// exactly once: a resize neither skips nor repeats it, and a
27    /// displacement, which moves an entry to a higher slot of its
28    /// neighborhood, links it there before unlinking it from its old slot (so
29    /// the walk meets it) and the walk recognizes a key it already met there
30    /// (so it is not repeated). An insert publishes its entry's hop bit before
31    /// it links the entry, so an entry the walk finds in a slot is one a
32    /// lookup finds too. An entry inserted, removed or updated concurrently may
33    /// or may not be reflected, and no key is yielded twice.
34    pub fn iter(&self) -> HopscotchIter<'_, K, V, S> {
35        let guard = pin();
36        let table = self.table.load(Ordering::Acquire, &guard).as_raw();
37        // SAFETY: loaded under `guard`, which the iterator keeps.
38        let current = unsafe { &*table };
39        let (slots, mask) = (current.buckets.len(), current.home_mask());
40        HopscotchIter {
41            table,
42            slots,
43            mask,
44            bucket_idx: 0,
45            recent: [core::ptr::null(); NEIGHBORHOOD_SIZE],
46            recent_hash: [0; NEIGHBORHOOD_SIZE],
47            same_key: <K as PartialEq>::eq,
48            guard,
49            _map: PhantomData,
50        }
51    }
52
53    /// Returns an iterator over the map keys.
54    pub fn keys(&self) -> HopscotchKeys<'_, K, V, S> {
55        HopscotchKeys { iter: self.iter() }
56    }
57
58    /// Returns an iterator over the map values (clones `V`).
59    pub fn values(&self) -> HopscotchValues<'_, K, V, S> {
60        HopscotchValues { iter: self.iter() }
61    }
62}
63
64/// Iterator over HopscotchMap entries ([`HopscotchMap::iter`]).
65pub struct HopscotchIter<'a, K: 'static, V: 'static, S> {
66    /// The table the walk started on, loaded under `guard`.
67    table: *const Table<K, V>,
68    /// `table`'s slot count and home mask, read once: the walk never reloads them.
69    slots: usize,
70    mask: usize,
71    bucket_idx: usize,
72    /// The entry the walk read in each of its last `NEIGHBORHOOD_SIZE` slots (slot `i` at
73    /// `i % NEIGHBORHOOD_SIZE`, null for a free slot),
74    /// loaded under `guard`: how the walk recognizes a key it already met.
75    recent: [*const Entry<K, V>; NEIGHBORHOOD_SIZE],
76    /// The hash of the entry in each slot of `recent` (meaningless for a free slot): the walk
77    /// compares hashes here, in its own memory, and reads an earlier entry only on a match.
78    recent_hash: [u64; NEIGHBORHOOD_SIZE],
79    /// `K`'s equality, taken where `iter` is built.
80    same_key: fn(&K, &K) -> bool,
81    guard: kovan::Guard,
82    _map: PhantomData<&'a HopscotchMap<K, V, S>>,
83}
84
85impl<K, V, S> HopscotchIter<'_, K, V, S> {
86    /// Whether the walk already met the key of `entry`, read at slot `idx`, in a lower slot of
87    /// the key's neighborhood (which starts at `home`). A move carries an entry to a higher slot
88    /// of its neighborhood, so the walk can meet it a second time there (or a newer entry of its
89    /// key, after an update or a re-insert). Every slot of that neighborhood below `idx` is still
90    /// in `recent`: the neighborhood spans `NEIGHBORHOOD_SIZE` slots.
91    fn met_before(&self, home: usize, idx: usize, entry: &Entry<K, V>) -> bool {
92        let lowest = home.max(idx.saturating_sub(NEIGHBORHOOD_SIZE - 1));
93        (lowest..idx).any(|seen_idx| {
94            let slot = seen_idx % NEIGHBORHOOD_SIZE;
95            if self.recent_hash[slot] != entry.hash {
96                return false;
97            }
98            let seen = self.recent[slot];
99            // SAFETY: null for a free slot, else loaded under `self.guard`, which keeps it from
100            // being freed.
101            unsafe { seen.as_ref() }.is_some_and(|seen| {
102                core::ptr::eq(seen, entry) || (self.same_key)(&seen.key, &entry.key)
103            })
104        })
105    }
106}
107
108impl<'a, K, V, S> Iterator for HopscotchIter<'a, K, V, S>
109where
110    K: Clone,
111    V: Clone,
112{
113    type Item = (K, V);
114
115    fn next(&mut self) -> Option<Self::Item> {
116        // SAFETY: owned by this iterator's `guard`, which was pinned before
117        // `table` was loaded and dies with the iterator: a resize that
118        // retires the table cannot free it (or its entries) while the guard
119        // is held.
120        let table = unsafe { &*self.table };
121
122        while self.bucket_idx < self.slots {
123            let idx = self.bucket_idx;
124            self.bucket_idx += 1;
125            let entry_ptr: *const Entry<K, V> = table
126                .get_bucket(idx)
127                .load(Ordering::Acquire, &self.guard)
128                .ptr();
129            // SAFETY: null for a free slot, else loaded under `self.guard`, which keeps it from
130            // being freed.
131            let Some(entry) = (unsafe { entry_ptr.as_ref() }) else {
132                // A free slot leaves its cell as it was. A stale cell (an entry met at least
133                // `NEIGHBORHOOD_SIZE` slots back) never matches: an entry's two slots in a move,
134                // or two entries of one key, share a home and so lie within one neighborhood,
135                // and `met_before` reads no cell below the home. The walk's guard keeps a stale
136                // entry allocated.
137                continue;
138            };
139            self.recent[idx % NEIGHBORHOOD_SIZE] = entry_ptr;
140            self.recent_hash[idx % NEIGHBORHOOD_SIZE] = entry.hash;
141            if !self.met_before((entry.hash as usize) & self.mask, idx, entry) {
142                return Some((entry.key.clone(), entry.value.clone()));
143            }
144        }
145        None
146    }
147}
148
149/// Iterator over HopscotchMap keys.
150pub struct HopscotchKeys<'a, K: 'static, V: 'static, S> {
151    iter: HopscotchIter<'a, K, V, S>,
152}
153
154impl<'a, K, V, S> Iterator for HopscotchKeys<'a, K, V, S>
155where
156    K: Clone,
157    V: Clone,
158{
159    type Item = K;
160
161    fn next(&mut self) -> Option<Self::Item> {
162        self.iter.next().map(|(k, _)| k)
163    }
164}
165
166/// Iterator over HopscotchMap values (clones `V`).
167pub struct HopscotchValues<'a, K: 'static, V: 'static, S> {
168    iter: HopscotchIter<'a, K, V, S>,
169}
170
171impl<'a, K, V, S> Iterator for HopscotchValues<'a, K, V, S>
172where
173    K: Clone,
174    V: Clone,
175{
176    type Item = V;
177
178    #[inline]
179    fn next(&mut self) -> Option<V> {
180        self.iter.next().map(|(_, v)| v)
181    }
182}
183
184/// Owned iterator yielding `(K, V)` by value - moves out of the entries, no
185/// clone. Each drained slot is nulled so the table destructor stays a no-op.
186pub struct HopscotchIntoIter<K: 'static, V: 'static> {
187    table: *mut Table<K, V>,
188    walk: Walk<K, V>,
189    guard: kovan::Guard,
190}
191
192impl<K, V> Iterator for HopscotchIntoIter<K, V> {
193    type Item = (K, V);
194
195    fn next(&mut self) -> Option<(K, V)> {
196        let table = unsafe { &*self.table };
197        while let Some((idx, entry)) = self.walk.next(table, &self.guard) {
198            if !entry.is_null() {
199                table.get_bucket(idx).store(Word::free(), Ordering::Relaxed);
200                let k = unsafe { core::ptr::read(&(*entry).key) };
201                let v = unsafe { core::ptr::read(&(*entry).value) };
202                unsafe {
203                    alloc::alloc::dealloc(
204                        entry as *mut u8,
205                        core::alloc::Layout::new::<Entry<K, V>>(),
206                    );
207                }
208                return Some((k, v));
209            }
210        }
211        None
212    }
213}
214
215impl<K, V> Drop for HopscotchIntoIter<K, V> {
216    fn drop(&mut self) {
217        while self.next().is_some() {}
218        // All slots nulled above; Table::drop frees only the bucket array.
219        unsafe { drop(Box::from_raw(self.table)) };
220    }
221}
222
223impl<K, V, S> IntoIterator for HopscotchMap<K, V, S>
224where
225    K: 'static,
226    V: 'static,
227{
228    type Item = (K, V);
229    type IntoIter = HopscotchIntoIter<K, V>;
230
231    fn into_iter(self) -> HopscotchIntoIter<K, V> {
232        let mut me = core::mem::ManuallyDrop::new(self);
233        let guard = pin();
234        let table = me.table.load(Ordering::Relaxed, &guard).as_raw();
235        unsafe { core::ptr::drop_in_place(&mut me.hasher) };
236        // SAFETY: the map is consumed, so the table is this iterator's alone.
237        let walk = Walk::new(unsafe { &*table }, &guard);
238        HopscotchIntoIter { table, walk, guard }
239    }
240}
241
242// `K: Send, V: Send`: an entry an insert replaces is retired, and its destructor may run on
243// another thread.
244impl<K, V, S> core::iter::FromIterator<(K, V)> for HopscotchMap<K, V, S>
245where
246    K: Hash + Eq + Clone + Send + 'static,
247    V: Clone + Send + 'static,
248    S: BuildHasher + Default,
249{
250    fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
251        let map = Self::with_hasher(S::default());
252        for (k, v) in iter {
253            map.insert(k, v);
254        }
255        map
256    }
257}
258
259// A concurrent walk yields owned clones (`K: Clone`, `V: Clone`) and recognizes a key it already
260// met (`K: Eq`, taken by `iter`), so these bounds are unavoidable here, unlike std's
261// unconstrained `IntoIterator for &HashMap`, which yields borrowed `(&K, &V)` and hashes nothing
262// at this bound-checked level either.
263impl<'a, K, V, S> IntoIterator for &'a HopscotchMap<K, V, S>
264where
265    K: Eq + Clone + 'static,
266    V: Clone + 'static,
267{
268    type Item = (K, V);
269    type IntoIter = HopscotchIter<'a, K, V, S>;
270
271    fn into_iter(self) -> Self::IntoIter {
272        self.iter()
273    }
274}