kovan_map/hashmap.rs
1//! High-Performance Lock-Free Concurrent Hash Map (FoldHash + resizable table).
2//!
3//! # Strategy
4//!
5//! 1. **FoldHash**: `foldhash::fast::FixedState` for fast, quality hashing.
6//! 2. **Resizable bucket table**: the bucket array lives in a single
7//! allocation (header + inline buckets) swapped atomically on resize and
8//! reclaimed through kovan. The map grows when the load factor exceeds
9//! 3/4 and shrinks below 1/4 (never under its initial capacity),
10//! mirroring `HopscotchMap`'s resize protocol.
11//! 3. **Optimized Node Layout**: fields ordered `hash -> key -> value -> next`
12//! to optimize cache line usage during checks.
13//!
14//! # Architecture
15//! - **Table**: kovan-retired object holding the bucket array (atomic head
16//! pointers). Readers snapshot the table under a guard and never block.
17//! - **Nodes**: singly linked chains, CAS-based lock-free insert/remove.
18//! - **Resize**: a single resizer (CAS on `resizing`) clones all entries
19//! into a new table, swaps the table pointer, and retires the old table;
20//! the old table's destructor frees its remaining chains exactly once at
21//! reclamation time. Writers wait out an active resize and re-validate
22//! after success so no update is lost to a concurrent migration.
23
24extern crate alloc;
25
26#[cfg(feature = "std")]
27extern crate std;
28
29use alloc::boxed::Box;
30use core::borrow::Borrow;
31use core::hash::{BuildHasher, Hash};
32use core::sync::atomic::{AtomicBool, AtomicIsize, Ordering, fence};
33use foldhash::fast::FixedState;
34use kovan::{Atomic, RetiredNode, Shared, pin, retire};
35
36// vertexia: `resizing` gates `try_resize` (CAS to claim the resize) and is
37// spun on by every other writer via `wait_for_resize`/`clear`'s CAS-retry
38// while a resize is in flight. That spin has no *other* yield point in it,
39// which is a problem under shuttle two levels deep:
40//
41// 1. Without any instrumented op in the loop, shuttle can't preempt out of
42// it at all -- a genuine hang once a writer observes `resizing == true`.
43// 2. Instrumenting the field itself (an earlier version of this fix swapped
44// `AtomicBool` for shuttle's) fixes (1) but isn't enough for *fairness*:
45// PCT keeps a thread's priority fixed except at a handful of preselected
46// "change points" or an explicit yield, so a plain instrumented `.load()`
47// in a spin loop can still be rescheduled indefinitely if it happens to
48// hold the higher priority, starving the resizer and running out
49// shuttle's step budget ("exceeded max_steps bound", an unfair schedule,
50// not a real bug).
51//
52// The fix needs a *yield*, not an instrumented load: `resize_spin_hint`
53// (below) calls `shuttle::hint::spin_loop`, which also calls
54// `shuttle::thread::yield_now`, which PCT treats as an explicit change
55// point, demoting the spinner's priority so the resizer is guaranteed a
56// turn -- independent of whether the *condition* it's spinning on is
57// instrumented. So `resizing` itself stays a plain `AtomicBool` under every
58// build, shuttle included: swapping its type is unnecessary for either
59// correctness or fairness here, and empirically, doing so anyway
60// introduced its own unrelated shuttle-only heap corruption in this crate's
61// shuttle test (reproduced independent of any resize ever triggering,
62// isolated by bisection, still unexplained -- plausibly a layout hazard
63// from shuttle's `AtomicBool` being a much larger `RefCell`-based type
64// instead of a 1-byte one; not chased further since the type swap was
65// never actually required).
66#[inline(always)]
67fn resize_spin_hint() {
68 #[cfg(feature = "shuttle")]
69 {
70 shuttle::hint::spin_loop();
71 }
72 #[cfg(not(feature = "shuttle"))]
73 {
74 core::hint::spin_loop();
75 }
76}
77
78/// Default number of buckets for `new()`. Matches the previous fixed-table
79/// sizing (zero collisions for ~100k items, fits in L3); maps created with
80/// `new()` keep exactly the old memory/performance profile and additionally
81/// grow past it on demand. Use [`HashMap::with_capacity`] for small elastic
82/// maps.
83const DEFAULT_CAPACITY: usize = 524_288;
84
85/// Minimum number of buckets; the map never shrinks below this.
86const MIN_CAPACITY: usize = 64;
87
88// Load-factor thresholds (implemented as integer comparisons on the hot
89// paths): grow when count/capacity > 3/4, shrink when count/capacity < 1/4
90// (never below the map's floor capacity). Same thresholds as HopscotchMap.
91
92/// A simple exponential backoff for reducing contention.
93struct Backoff {
94 step: u32,
95}
96
97impl Backoff {
98 #[inline(always)]
99 fn new() -> Self {
100 Self { step: 0 }
101 }
102
103 #[inline(always)]
104 fn spin(&mut self) {
105 for _ in 0..(1 << self.step.min(6)) {
106 core::hint::spin_loop();
107 }
108 if self.step <= 6 {
109 self.step += 1;
110 }
111 }
112}
113
114/// Node in the lock-free linked list.
115#[repr(C)]
116struct Node<K, V> {
117 retired: RetiredNode,
118 hash: u64,
119 key: K,
120 value: V,
121 next: Atomic<Node<K, V>>,
122}
123
124// SAFETY (kovan retirement rule): a retired Node's destructor may run on
125// any thread, and nodes (with K and V inside) move between threads — hence
126// `K: Send, V: Send` for Send. Unlike exclusive-transfer containers,
127// lookups DO produce `&K`/`&V` from a shared `&Node` (get() clones V
128// through &V under concurrent readers), so Sync additionally requires
129// `K: Sync, V: Sync` — the same bounds the map-level Sync impl below has
130// always required for sharing the map.
131unsafe impl<K: Send, V: Send> Send for Node<K, V> {}
132unsafe impl<K: Send + Sync, V: Send + Sync> Sync for Node<K, V> {}
133
134// ---------------------------------------------------------------------------
135// Harris-style logical deletion
136// ---------------------------------------------------------------------------
137//
138// Removing (or replacing) a node first TAGS the victim's `next` pointer
139// (low bit set) — the logical delete — and only then unlinks it from its
140// predecessor. The thread whose tag-CAS succeeded exclusively owns the node
141// and is the only one to `retire()` it. This closes two races a plain
142// unlink-CAS protocol has:
143//
144// * insert-after-removed-tail: a tail insert CASes `tail.next: null -> new`;
145// if the tail was concurrently unlinked and retired, the new node is
146// spliced onto dead memory — the insert is lost and the node leaks.
147// With tagging, the remover first turns the tail's `next` into
148// tagged-null, so the insert's CAS (expecting untagged null) fails.
149//
150// * adjacent removes: removing B (A->B->C) and C (B->C->D) concurrently
151// can unlink C from the already-detached B while C is still reachable
152// through A, retiring a reachable node (use-after-free for later
153// readers). With tagging, C's remover owns C via the tag; walkers
154// observe `B.next` tagged and never operate relative to deleted nodes.
155//
156// Invariants:
157// * tags appear only on `Node.next` fields, never on bucket heads
158// (snipping stores the untagged successor);
159// * a node whose `next` is tagged has been retired by its tag owner —
160// `clear()`, the migration sweep, and `Table::drop` must skip it;
161// * every traversal untags before following a `next` pointer.
162
163#[inline(always)]
164fn tagged<K, V>(p: *mut Node<K, V>) -> *mut Node<K, V> {
165 (p as usize | 1) as *mut Node<K, V>
166}
167
168#[inline(always)]
169fn untag<K, V>(p: *mut Node<K, V>) -> *mut Node<K, V> {
170 (p as usize & !1) as *mut Node<K, V>
171}
172
173#[inline(always)]
174fn is_tagged<K, V>(p: *const Node<K, V>) -> bool {
175 (p as usize) & 1 != 0
176}
177
178// ---------------------------------------------------------------------------
179// Single-allocation table: [TableHeader][Atomic<Node>; capacity]
180// ---------------------------------------------------------------------------
181//
182// The header and the bucket array share one allocation, so the read path is
183// `table ptr -> header line (mask, hot in cache) -> bucket line` — the same
184// number of cold dereferences as a fixed embedded array. The table pointer
185// itself is swapped atomically on resize.
186//
187// Reclamation goes through a tiny boxed `TableProxy` (RetiredNode at offset
188// 0, as `retire()` requires): retiring the proxy defers until every guard
189// that could observe the old table has been released; the proxy's destructor
190// then frees the table's remaining chains and the allocation itself.
191//
192// The proxy is built eagerly, in `TableRef::alloc`, at the SAME time as the
193// table it guards, not lazily when the table is finally retired. See the
194// long comment on `alloc` for why: a proxy built at retire time stamps its
195// birth_epoch too late, and kovan can then judge a straggler writer as not
196// needing protection for a table it is still actively CASing into.
197
198#[repr(C)]
199struct TableHeader {
200 mask: usize,
201 capacity: usize,
202 /// Type-erased `*mut TableProxy<K, V>` for this table's eventual
203 /// retirement (see `TableRef::alloc`). Zero means already taken/freed.
204 proxy: usize,
205}
206
207/// Borrowed view of a table allocation.
208struct TableRef<K: 'static, V: 'static> {
209 header: *mut TableHeader,
210 _marker: core::marker::PhantomData<(K, V)>,
211}
212
213impl<K, V> Clone for TableRef<K, V> {
214 fn clone(&self) -> Self {
215 *self
216 }
217}
218impl<K, V> Copy for TableRef<K, V> {}
219
220impl<K: 'static, V: 'static> TableRef<K, V> {
221 #[inline(always)]
222 fn from_raw(header: *mut TableHeader) -> Self {
223 Self {
224 header,
225 _marker: core::marker::PhantomData,
226 }
227 }
228
229 fn layout(capacity: usize) -> (core::alloc::Layout, usize) {
230 let header = core::alloc::Layout::new::<TableHeader>();
231 let buckets = core::alloc::Layout::array::<Atomic<Node<K, V>>>(capacity)
232 .expect("bucket array layout overflow");
233 let (layout, offset) = header.extend(buckets).expect("table layout overflow");
234 (layout.pad_to_align(), offset)
235 }
236
237 /// Allocate a zero-initialized table (`Atomic` buckets zero == null) and
238 /// eagerly build its retirement proxy.
239 ///
240 /// The proxy is built *here*, at the table's own birth, not lazily at
241 /// `try_resize` time. `RetiredNode::new()` stamps `birth_epoch` from the
242 /// calling thread's cached epoch at construction time (kovan's
243 /// contract: "birth_epoch must be set at allocation time, not
244 /// retirement time" (see `kovan::RetiredNode::new`). A table can live
245 /// through many other threads' operations before it is ever resized
246 /// away; if its proxy were only constructed when the resizer finally
247 /// retires it, the proxy's birth_epoch would be the *resizer's* current
248 /// epoch, which can be arbitrarily newer than the epoch a straggler
249 /// writer published the last time it observed this table via
250 /// `self.table.load()`. kovan's eligibility check
251 /// (`slot.epoch >= min_epoch`) would then wrongly judge that straggler
252 /// as not needing protection, and its `TableProxy` could be reclaimed
253 /// while the straggler is still mid-CAS on the table's own memory.
254 /// Stamping the proxy at the table's own allocation predates every
255 /// straggler that could ever see this table, closing the gap: the
256 /// same guarantee `HopscotchMap::try_resize` gets for free by retiring
257 /// its table struct directly (`RetiredNode` embedded at construction).
258 fn alloc(capacity: usize) -> Self {
259 let capacity = capacity.next_power_of_two().max(MIN_CAPACITY);
260 let (layout, _) = Self::layout(capacity);
261 // SAFETY: layout is non-zero sized; zeroed AtomicUsize == null bucket.
262 let header = unsafe { alloc::alloc::alloc_zeroed(layout) as *mut TableHeader };
263 assert!(!header.is_null(), "table allocation failed");
264 unsafe {
265 (*header).mask = capacity - 1;
266 (*header).capacity = capacity;
267 }
268 let table = Self::from_raw(header);
269 let proxy = Box::into_raw(Box::new(TableProxy {
270 retired: RetiredNode::new(),
271 table,
272 }));
273 unsafe { (*header).proxy = proxy as usize };
274 table
275 }
276
277 /// Take this table's pre-built retirement proxy, for a single upcoming
278 /// `retire()` call. Must be called at most once per table.
279 #[inline(always)]
280 fn take_proxy(self) -> *mut TableProxy<K, V> {
281 let proxy = unsafe { (*self.header).proxy };
282 assert_ne!(proxy, 0, "kovan-map: table proxy already taken");
283 unsafe { (*self.header).proxy = 0 };
284 proxy as *mut TableProxy<K, V>
285 }
286
287 /// Free this table's proxy directly (without running its `Drop`, which
288 /// would call back into `free`/`free_array_only` on this same table) if
289 /// it was never retired. Used on every path where a table dies without
290 /// ever being resized away: `HashMap::drop`, `IntoIter`, and a
291 /// discarded, never-published resize target. No-op if `take_proxy` was
292 /// already called (the normal resize-retirement path).
293 #[inline(always)]
294 unsafe fn drop_unused_proxy(self) {
295 let proxy = unsafe { (*self.header).proxy };
296 if proxy != 0 {
297 unsafe {
298 (*self.header).proxy = 0;
299 alloc::alloc::dealloc(
300 proxy as *mut u8,
301 core::alloc::Layout::new::<TableProxy<K, V>>(),
302 );
303 }
304 }
305 }
306
307 /// Free remaining chains (skipping tagged nodes — already retired by
308 /// their tag owners) and the allocation itself.
309 ///
310 /// Free only the table allocation, not the chains (caller already drained
311 /// the live nodes, e.g. `IntoIter`). Tagged nodes remain kovan-owned.
312 ///
313 /// # Safety
314 /// Exclusive access; the chains must already be drained.
315 unsafe fn free_array_only(self) {
316 unsafe { self.drop_unused_proxy() };
317 let capacity = unsafe { (*self.header).capacity };
318 let (layout, _) = Self::layout(capacity);
319 unsafe { alloc::alloc::dealloc(self.header as *mut u8, layout) };
320 }
321
322 /// # Safety
323 /// Caller must have exclusive access (map drop, or proxy reclamation
324 /// after guard quiescence).
325 unsafe fn free(self) {
326 unsafe { self.drop_unused_proxy() };
327 let capacity = unsafe { (*self.header).capacity };
328 let guard = pin();
329 for i in 0..capacity {
330 let mut current = self.bucket(i).load(Ordering::Relaxed, &guard).as_raw();
331 while !current.is_null() {
332 unsafe {
333 let next = (*current).next.load(Ordering::Relaxed, &guard).as_raw();
334 if !is_tagged(next) {
335 drop(Box::from_raw(current));
336 }
337 current = untag(next);
338 }
339 }
340 }
341 drop(guard);
342 let (layout, _) = Self::layout(capacity);
343 unsafe { alloc::alloc::dealloc(self.header as *mut u8, layout) };
344 }
345
346 #[inline(always)]
347 fn as_raw(self) -> *mut TableHeader {
348 self.header
349 }
350
351 #[inline(always)]
352 fn capacity(self) -> usize {
353 unsafe { (*self.header).capacity }
354 }
355
356 #[inline(always)]
357 fn buckets(self) -> *const Atomic<Node<K, V>> {
358 let (_, offset) = Self::layout_offset();
359 unsafe { (self.header as *const u8).add(offset) as *const Atomic<Node<K, V>> }
360 }
361
362 /// Header/bucket offset is capacity-independent; compute it once.
363 #[inline(always)]
364 fn layout_offset() -> ((), usize) {
365 let header = core::alloc::Layout::new::<TableHeader>();
366 let one = core::alloc::Layout::new::<Atomic<Node<K, V>>>();
367 let (_, offset) = header.extend(one).expect("layout");
368 ((), offset)
369 }
370
371 #[inline(always)]
372 fn bucket_index(self, hash: u64) -> usize {
373 (hash as usize) & unsafe { (*self.header).mask }
374 }
375
376 #[inline(always)]
377 fn bucket(self, idx: usize) -> &'static Atomic<Node<K, V>> {
378 // SAFETY: idx is masked or bounded by capacity; the 'static is a
379 // lie scoped by the caller's guard (same discipline as Shared).
380 unsafe { &*self.buckets().add(idx) }
381 }
382}
383
384/// Reclamation proxy for a table allocation (RetiredNode at offset 0).
385#[repr(C)]
386struct TableProxy<K: 'static, V: 'static> {
387 retired: RetiredNode,
388 table: TableRef<K, V>,
389}
390
391// SAFETY (kovan retirement rule): the proxy's destructor (running on any
392// thread) frees the table's nodes, hence K, V: Send.
393unsafe impl<K: Send, V: Send> Send for TableProxy<K, V> {}
394unsafe impl<K: Send + Sync, V: Send + Sync> Sync for TableProxy<K, V> {}
395
396impl<K, V> Drop for TableProxy<K, V> {
397 fn drop(&mut self) {
398 // SAFETY: kovan reclaimed the proxy only after every guard that
399 // could observe the old table has been released.
400 unsafe { self.table.free() };
401 }
402}
403
404/// High-Performance Lock-Free Map with automatic grow/shrink.
405pub struct HashMap<K: 'static, V: 'static, S = FixedState> {
406 table: Atomic<TableHeader>,
407 /// Approximate live-entry count driving the resize thresholds.
408 /// Signed: a transient negative under racing removes is harmless and
409 /// avoids a CAS loop (fetch_update) on the hot remove path.
410 count: AtomicIsize,
411 /// Single-resizer latch; writers wait while a resize is in flight.
412 resizing: AtomicBool,
413 /// Shrink floor: the initial capacity. The map never shrinks below the
414 /// size it was created with, preserving the caller's sizing intent (and
415 /// the historical fixed-table behavior for `new()`).
416 floor: usize,
417 hasher: S,
418 _marker: core::marker::PhantomData<(K, V)>,
419}
420
421#[cfg(feature = "std")]
422impl<K, V> HashMap<K, V, FixedState>
423where
424 K: Hash + Eq + Clone + 'static,
425 V: Clone + 'static,
426{
427 /// Creates a new empty hash map with FoldHash (FixedState).
428 pub fn new() -> Self {
429 Self::with_hasher(FixedState::default())
430 }
431
432 /// Creates a new empty hash map with at least `capacity` buckets.
433 pub fn with_capacity(capacity: usize) -> Self {
434 Self::with_capacity_and_hasher(capacity, FixedState::default())
435 }
436}
437
438impl<K, V, S> HashMap<K, V, S>
439where
440 K: Hash + Eq + Clone + 'static,
441 V: Clone + 'static,
442 S: BuildHasher,
443{
444 /// Creates a new hash map with custom hasher.
445 pub fn with_hasher(hasher: S) -> Self {
446 Self::with_capacity_and_hasher(DEFAULT_CAPACITY, hasher)
447 }
448
449 /// Creates a new hash map with at least `capacity` buckets and a custom hasher.
450 ///
451 /// The map grows when its load factor exceeds 0.75 and shrinks when it
452 /// falls below 0.25 — but never below `capacity`.
453 pub fn with_capacity_and_hasher(capacity: usize, hasher: S) -> Self {
454 let table = TableRef::<K, V>::alloc(capacity);
455 let floor = table.capacity();
456 Self {
457 table: Atomic::new(table.as_raw()),
458 count: AtomicIsize::new(0),
459 resizing: AtomicBool::new(false),
460 floor,
461 hasher,
462 _marker: core::marker::PhantomData,
463 }
464 }
465
466 /// Returns the current number of buckets.
467 pub fn capacity(&self) -> usize {
468 let guard = pin();
469 let table = TableRef::<K, V>::from_raw(self.table.load(Ordering::Acquire, &guard).as_raw());
470 table.capacity()
471 }
472
473 /// Spin until any in-flight resize completes.
474 #[inline]
475 fn wait_for_resize(&self) {
476 while self.resizing.load(Ordering::Acquire) {
477 resize_spin_hint();
478 }
479 }
480
481 /// Optimized get operation. Never blocks — reads the current table
482 /// snapshot under a guard, even while a resize is in flight.
483 pub fn get<Q>(&self, key: &Q) -> Option<V>
484 where
485 K: Borrow<Q>,
486 Q: Hash + Eq + ?Sized,
487 {
488 let hash = self.hasher.hash_one(key);
489 let guard = pin();
490 let table = TableRef::<K, V>::from_raw(self.table.load(Ordering::Acquire, &guard).as_raw());
491 let bucket = table.bucket(table.bucket_index(hash));
492
493 let mut current = bucket.load(Ordering::Acquire, &guard).as_raw();
494 while !current.is_null() {
495 unsafe {
496 let node = &*current;
497 // Check hash first (integer compare is fast). Matching a
498 // logically-deleted node is linearizable (the read happened
499 // before the delete), so no tag check on the match path.
500 if node.hash == hash && node.key.borrow() == key {
501 return Some(node.value.clone());
502 }
503 // Untag: the pointer may carry the deletion tag.
504 current = untag(node.next.load(Ordering::Acquire, &guard).as_raw());
505 }
506 }
507 None
508 }
509
510 /// Checks if the key exists.
511 pub fn contains_key<Q>(&self, key: &Q) -> bool
512 where
513 K: Borrow<Q>,
514 Q: Hash + Eq + ?Sized,
515 {
516 self.get(key).is_some()
517 }
518
519 /// Insert a key-value pair.
520 pub fn insert(&self, key: K, value: V) -> Option<V> {
521 let hash = self.hasher.hash_one(&key);
522 let mut backoff = Backoff::new();
523 // Count a new key exactly once across re-validation retries
524 // (mirrors HopscotchMap: prevents both under-count, which causes
525 // cascading resizes, and double-count).
526 let mut counted = false;
527 // The first successful op's previous value is the linearized result;
528 // re-validation retries may replace a migrated clone of it.
529 let mut result: Option<Option<V>> = None;
530
531 'outer: loop {
532 self.wait_for_resize();
533
534 let guard = pin();
535 let table_raw = self.table.load(Ordering::Acquire, &guard).as_raw();
536 let table = TableRef::<K, V>::from_raw(table_raw);
537 if self.resizing.load(Ordering::Acquire) {
538 continue;
539 }
540
541 let bucket = table.bucket(table.bucket_index(hash));
542
543 // 1. Search for existing key to update (snip-walk: physically
544 // unlink logically-deleted nodes as we pass them).
545 let mut prev_link = bucket;
546 let mut current = prev_link.load(Ordering::Acquire, &guard).as_raw();
547
548 while !current.is_null() {
549 unsafe {
550 let node = &*current;
551 let next = node.next.load(Ordering::Acquire, &guard).as_raw();
552
553 if is_tagged(next) {
554 // Logically deleted: snip it out (its tag owner has
555 // already retired it). On contention restart the scan.
556 if prev_link
557 .compare_exchange(
558 Shared::from_raw(current),
559 Shared::from_raw(untag(next)),
560 Ordering::AcqRel,
561 Ordering::Relaxed,
562 &guard,
563 )
564 .is_err()
565 {
566 backoff.spin();
567 continue 'outer;
568 }
569 current = untag(next);
570 continue;
571 }
572
573 if node.hash == hash && node.key == key {
574 // Replace: logically delete the old node (tag-CAS
575 // makes us its exclusive owner), then swing the
576 // predecessor to the replacement in one step.
577 let old_value = node.value.clone();
578 if node
579 .next
580 .compare_exchange(
581 Shared::from_raw(next),
582 Shared::from_raw(tagged(next)),
583 Ordering::AcqRel,
584 Ordering::Relaxed,
585 &guard,
586 )
587 .is_err()
588 {
589 // Someone else deleted/replaced it first.
590 backoff.spin();
591 continue 'outer;
592 }
593 // We own the old node now — we retire it, exactly once.
594 let new_node = Box::into_raw(Box::new(Node {
595 retired: RetiredNode::new(),
596 hash,
597 key: key.clone(),
598 value: value.clone(),
599 next: Atomic::new(next),
600 }));
601 let swapped = prev_link
602 .compare_exchange(
603 Shared::from_raw(current),
604 Shared::from_raw(new_node),
605 Ordering::AcqRel,
606 Ordering::Relaxed,
607 &guard,
608 )
609 .is_ok();
610 // SAFETY: tag ownership; Node is #[repr(C)] with
611 // RetiredNode at offset 0.
612 retire(current);
613 if result.is_none() {
614 result = Some(Some(old_value));
615 }
616 if !swapped {
617 // A helper snipped the old node before our swing;
618 // the replacement is not installed — retry the
619 // whole op (the removal already linearized).
620 drop(Box::from_raw(new_node));
621 backoff.spin();
622 continue 'outer;
623 }
624 // Dekker/SB fence: pairs with the matching fence in
625 // `try_resize`, placed right after it claims
626 // `resizing`. Without both fences, the swing-CAS
627 // above (a store) and the resizing/table loads just
628 // below are this thread's store-then-load half of a
629 // race against the resizer's own store-then-load
630 // half (claim `resizing`, then read this bucket
631 // during the sweep): plain AcqRel/Acquire lets both
632 // sides observe the pre-update value of the other's
633 // write (the classic store-buffering litmus test),
634 // so the sweep could miss this mutation while we
635 // simultaneously miss that a resize is in flight,
636 // silently orphaning the update in the table being
637 // retired. The fence forces this CAS and the
638 // resizer's `resizing` claim into the same SeqCst
639 // total order, so at least one side is guaranteed to
640 // observe the other. x86 TSO hides the gap (every
641 // CAS there is already a full fence); ARM's AcqRel
642 // is not.
643 fence(Ordering::SeqCst);
644 // Re-validate: if a resize started (or completed)
645 // since we loaded the table, the migration may have
646 // cloned the entry before our update — redo the op
647 // on the new table so the update is not lost.
648 if self.resizing.load(Ordering::SeqCst)
649 || self.table.load(Ordering::SeqCst, &guard).as_raw() != table_raw
650 {
651 continue 'outer;
652 }
653 return result.unwrap();
654 }
655
656 prev_link = &node.next;
657 current = next;
658 }
659 }
660
661 // 2. Key not found. Insert at TAIL (prev_link). The CAS expects
662 // an untagged null, so it fails if the tail node was
663 // concurrently logically deleted.
664 let new_node_ptr = Box::into_raw(Box::new(Node {
665 retired: RetiredNode::new(),
666 hash,
667 key: key.clone(),
668 value: value.clone(),
669 next: Atomic::null(),
670 }));
671
672 match prev_link.compare_exchange(
673 unsafe { Shared::from_raw(core::ptr::null_mut()) },
674 unsafe { Shared::from_raw(new_node_ptr) },
675 Ordering::Release,
676 Ordering::Relaxed,
677 &guard,
678 ) {
679 Ok(_) => {
680 if !counted {
681 counted = true;
682 self.count.fetch_add(1, Ordering::Relaxed);
683 }
684 if result.is_none() {
685 result = Some(None);
686 }
687 // Dekker/SB fence pairing with try_resize's claim-side
688 // fence (full justification on the replace path above):
689 // the tail-append CAS above is this thread's store-side
690 // of the same store-load race.
691 fence(Ordering::SeqCst);
692 // Re-validate against a concurrent migration (see above).
693 if self.resizing.load(Ordering::SeqCst)
694 || self.table.load(Ordering::SeqCst, &guard).as_raw() != table_raw
695 {
696 continue 'outer;
697 }
698
699 // Grow check (only when we actually added an entry).
700 let new_count = self.count.load(Ordering::Relaxed).max(0) as usize;
701 let capacity = table.capacity();
702 // Integer load-factor check: count/cap > 3/4.
703 if 4 * new_count > 3 * capacity {
704 drop(guard);
705 self.try_resize(capacity * 2);
706 }
707 return result.unwrap();
708 }
709 Err(_) => {
710 // Contention at the tail — retry the search/append loop.
711 unsafe {
712 drop(Box::from_raw(new_node_ptr));
713 }
714 backoff.spin();
715 continue 'outer;
716 }
717 }
718 }
719 }
720
721 /// Insert a key-value pair only if the key does not exist.
722 /// Returns `None` if inserted, `Some(existing_value)` if the key already exists.
723 pub fn insert_if_absent(&self, key: K, value: V) -> Option<V> {
724 let hash = self.hasher.hash_one(&key);
725 let mut backoff = Backoff::new();
726 let mut counted = false;
727
728 'outer: loop {
729 self.wait_for_resize();
730
731 let guard = pin();
732 let table_raw = self.table.load(Ordering::Acquire, &guard).as_raw();
733 let table = TableRef::<K, V>::from_raw(table_raw);
734 if self.resizing.load(Ordering::Acquire) {
735 continue;
736 }
737
738 let bucket = table.bucket(table.bucket_index(hash));
739
740 // 1. Search for existing key (snip-walk).
741 let mut prev_link = bucket;
742 let mut current = prev_link.load(Ordering::Acquire, &guard).as_raw();
743
744 while !current.is_null() {
745 unsafe {
746 let node = &*current;
747 let next = node.next.load(Ordering::Acquire, &guard).as_raw();
748
749 if is_tagged(next) {
750 if prev_link
751 .compare_exchange(
752 Shared::from_raw(current),
753 Shared::from_raw(untag(next)),
754 Ordering::AcqRel,
755 Ordering::Relaxed,
756 &guard,
757 )
758 .is_err()
759 {
760 backoff.spin();
761 continue 'outer;
762 }
763 current = untag(next);
764 continue;
765 }
766
767 if node.hash == hash && node.key == key {
768 // Found on a retry. This may be our own migrated
769 // clone OR another caller's entry that landed while
770 // the table swapped - the two are indistinguishable
771 // here, and reporting None for someone else's entry
772 // would admit a second winner. Return the canonical
773 // value either way (for our own clone that is a
774 // clone of the value we just inserted), matching
775 // HopscotchMap's retry semantics: under a concurrent
776 // resize a successful insert may report
777 // Some(its own value); callers must treat the
778 // returned value as canonical.
779 return Some(node.value.clone());
780 }
781 prev_link = &node.next;
782 current = next;
783 }
784 }
785
786 // 2. Key not found (or our pre-migration insert was not carried
787 // over) — insert at TAIL. Untagged-null expectation makes the
788 // CAS fail if the tail was concurrently logically deleted.
789 let new_node_ptr = Box::into_raw(Box::new(Node {
790 retired: RetiredNode::new(),
791 hash,
792 key: key.clone(),
793 value: value.clone(),
794 next: Atomic::null(),
795 }));
796
797 match prev_link.compare_exchange(
798 unsafe { Shared::from_raw(core::ptr::null_mut()) },
799 unsafe { Shared::from_raw(new_node_ptr) },
800 Ordering::Release,
801 Ordering::Relaxed,
802 &guard,
803 ) {
804 Ok(_) => {
805 if !counted {
806 counted = true;
807 self.count.fetch_add(1, Ordering::Relaxed);
808 }
809 // Dekker/SB fence pairing with try_resize's claim-side
810 // fence (full justification in HashMap::insert's replace
811 // path): the tail-append CAS above is this thread's
812 // store-side of the same store-load race.
813 fence(Ordering::SeqCst);
814 // Re-validate against a concurrent migration.
815 if self.resizing.load(Ordering::SeqCst)
816 || self.table.load(Ordering::SeqCst, &guard).as_raw() != table_raw
817 {
818 continue 'outer;
819 }
820
821 let new_count = self.count.load(Ordering::Relaxed).max(0) as usize;
822 let capacity = table.capacity();
823 // Integer load-factor check: count/cap > 3/4.
824 if 4 * new_count > 3 * capacity {
825 drop(guard);
826 self.try_resize(capacity * 2);
827 }
828 return None;
829 }
830 Err(actual_val) => {
831 // Contention at the tail.
832 unsafe {
833 let appended_ptr = actual_val.as_raw();
834 drop(Box::from_raw(new_node_ptr));
835 if !is_tagged(appended_ptr) && !appended_ptr.is_null() {
836 let appended = &*appended_ptr;
837 if appended.hash == hash && appended.key == key {
838 // Race lost, key exists now. Same canonical-
839 // value rule as the retry-found path above:
840 // even if a pre-migration attempt of ours
841 // inserted, the surviving entry is what
842 // every caller must converge on.
843 return Some(appended.value.clone());
844 }
845 }
846 }
847 backoff.spin();
848 continue 'outer;
849 }
850 }
851 }
852 }
853
854 /// Returns the value corresponding to the key, or inserts the given value if the key is not present.
855 ///
856 /// This is linearizable: concurrent callers for the same key are guaranteed to
857 /// agree on which value was inserted (exactly one thread's CAS succeeds at the
858 /// list tail, and all others see that node on retry).
859 pub fn get_or_insert(&self, key: K, value: V) -> V {
860 match self.insert_if_absent(key, value.clone()) {
861 Some(existing) => existing,
862 None => value,
863 }
864 }
865
866 /// Remove a key-value pair.
867 pub fn remove<Q>(&self, key: &Q) -> Option<V>
868 where
869 K: Borrow<Q>,
870 Q: Hash + Eq + ?Sized,
871 {
872 let hash = self.hasher.hash_one(key);
873 let mut backoff = Backoff::new();
874 // First successful removal's value is the linearized result;
875 // re-validation retries only evict migrated clones.
876 let mut result: Option<V> = None;
877
878 'outer: loop {
879 self.wait_for_resize();
880
881 let guard = pin();
882 let table_raw = self.table.load(Ordering::Acquire, &guard).as_raw();
883 let table = TableRef::<K, V>::from_raw(table_raw);
884 if self.resizing.load(Ordering::Acquire) {
885 continue;
886 }
887
888 let bucket = table.bucket(table.bucket_index(hash));
889
890 let mut prev_link = bucket;
891 let mut current = prev_link.load(Ordering::Acquire, &guard).as_raw();
892
893 while !current.is_null() {
894 unsafe {
895 let node = &*current;
896 let next = node.next.load(Ordering::Acquire, &guard).as_raw();
897
898 if is_tagged(next) {
899 // Logically deleted by someone else: snip and move on.
900 if prev_link
901 .compare_exchange(
902 Shared::from_raw(current),
903 Shared::from_raw(untag(next)),
904 Ordering::AcqRel,
905 Ordering::Relaxed,
906 &guard,
907 )
908 .is_err()
909 {
910 backoff.spin();
911 continue 'outer;
912 }
913 current = untag(next);
914 continue;
915 }
916
917 if node.hash == hash && node.key.borrow() == key {
918 let old_value = node.value.clone();
919
920 // Logical delete: tag the victim's next. The tag
921 // owner — and only the tag owner — retires the node,
922 // and the tag makes concurrent tail-inserts onto
923 // this node fail.
924 if node
925 .next
926 .compare_exchange(
927 Shared::from_raw(next),
928 Shared::from_raw(tagged(next)),
929 Ordering::AcqRel,
930 Ordering::Relaxed,
931 &guard,
932 )
933 .is_err()
934 {
935 backoff.spin();
936 continue 'outer;
937 }
938
939 // Physical unlink (best effort — if it fails, a
940 // later walker snips it).
941 let _ = prev_link.compare_exchange(
942 Shared::from_raw(current),
943 Shared::from_raw(next),
944 Ordering::AcqRel,
945 Ordering::Relaxed,
946 &guard,
947 );
948
949 // SAFETY: tag ownership; Node is #[repr(C)] with
950 // RetiredNode at offset 0.
951 retire(current);
952 if result.is_none() {
953 result = Some(old_value);
954 }
955
956 // Single atomic decrement (signed counter — cannot
957 // wrap; a transient negative just clamps to 0 below).
958 let new_count =
959 (self.count.fetch_sub(1, Ordering::Relaxed) - 1).max(0) as usize;
960 // Integer load-factor check: count/cap < 1/4.
961 let shrink_to = (4 * new_count < table.capacity()
962 && table.capacity() > self.floor)
963 .then_some(table.capacity() / 2);
964
965 // Dekker/SB fence pairing with try_resize's
966 // claim-side fence (full justification in
967 // HashMap::insert's replace path): the tag-CAS above
968 // is this thread's store-side of the same
969 // store-load race, so the same "sweep misses the
970 // mutation and we miss the resize" window applies
971 // here, and the resurrection this re-validation
972 // exists to catch would otherwise go undetected.
973 fence(Ordering::SeqCst);
974 // Re-validate: a concurrent migration may have cloned
975 // this entry into the new table before we deleted it
976 // here — redo the removal on the current table so the
977 // key does not resurrect.
978 if self.resizing.load(Ordering::SeqCst)
979 || self.table.load(Ordering::SeqCst, &guard).as_raw() != table_raw
980 {
981 continue 'outer;
982 }
983
984 if let Some(cap) = shrink_to {
985 drop(guard);
986 self.try_resize(cap);
987 }
988 return result;
989 }
990
991 prev_link = &node.next;
992 current = next;
993 }
994 }
995
996 // Key not present in the current table.
997 return result;
998 }
999 }
1000
1001 /// Remove **all** nodes matching `key`, returning the most recent value
1002 /// if the key was present.
1003 ///
1004 /// [`remove`](Self::remove) unlinks only the first matching entry.
1005 /// Insert/remove races can transiently leave more than one entry for
1006 /// the same key ("versions"); after a plain `remove()` an older version
1007 /// would become visible again. This method keeps removing until a full
1008 /// scan finds no match, so the key is guaranteed absent at the
1009 /// linearization point of the final scan.
1010 ///
1011 /// Use `remove()` for single-version removal semantics and
1012 /// `force_remove()` when the key must be fully evicted.
1013 ///
1014 /// Note: a concurrent `insert` of the same key can land after the final
1015 /// scan, as with any removal under contention.
1016 pub fn force_remove<Q>(&self, key: &Q) -> Option<V>
1017 where
1018 K: Borrow<Q>,
1019 Q: Hash + Eq + ?Sized,
1020 {
1021 let mut newest = None;
1022 loop {
1023 match self.remove(key) {
1024 Some(v) => {
1025 // The first removal unlinks the first match in scan
1026 // order — the live (most recent) version.
1027 if newest.is_none() {
1028 newest = Some(v);
1029 }
1030 }
1031 None => return newest,
1032 }
1033 }
1034 }
1035
1036 /// Clear the map.
1037 pub fn clear(&self) {
1038 // Take the resize latch so the table cannot be swapped (and no
1039 // writer is mid-migration) while we unlink the chains.
1040 while self
1041 .resizing
1042 .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
1043 .is_err()
1044 {
1045 resize_spin_hint();
1046 }
1047
1048 let guard = pin();
1049 let table = TableRef::<K, V>::from_raw(self.table.load(Ordering::Acquire, &guard).as_raw());
1050
1051 for i in 0..table.capacity() {
1052 let bucket = table.bucket(i);
1053 loop {
1054 let head = bucket.load(Ordering::Acquire, &guard);
1055 if head.is_null() {
1056 break;
1057 }
1058
1059 // Try to unlink the whole chain at once
1060 match bucket.compare_exchange(
1061 head,
1062 unsafe { Shared::from_raw(core::ptr::null_mut()) },
1063 Ordering::Release,
1064 Ordering::Relaxed,
1065 &guard,
1066 ) {
1067 Ok(_) => {
1068 // Retire the chain's live nodes. Tagged nodes were
1069 // already retired by their tag owners — skip them.
1070 unsafe {
1071 let mut current = head.as_raw();
1072 while !current.is_null() {
1073 let next = (*current).next.load(Ordering::Relaxed, &guard).as_raw();
1074 if !is_tagged(next) {
1075 // SAFETY: allocated via Box::into_raw;
1076 // Node is #[repr(C)], RetiredNode first.
1077 retire(current);
1078 }
1079 current = untag(next);
1080 }
1081 }
1082 break;
1083 }
1084 Err(_) => {
1085 // Contention, retry
1086 continue;
1087 }
1088 }
1089 }
1090 }
1091
1092 self.count.store(0, Ordering::Release);
1093 self.resizing.store(false, Ordering::Release);
1094 }
1095
1096 /// Returns true if the map is empty.
1097 pub fn is_empty(&self) -> bool {
1098 self.len() == 0
1099 }
1100
1101 /// Returns the number of elements in the map.
1102 ///
1103 /// O(1): maintained by insert/remove. Approximate while concurrent
1104 /// updates are in flight (exact in quiescence), like `HopscotchMap`.
1105 pub fn len(&self) -> usize {
1106 self.count.load(Ordering::Relaxed).max(0) as usize
1107 }
1108
1109 /// Resize the table to `new_capacity` buckets (single resizer wins).
1110 ///
1111 /// Clones every entry into a new table, swaps the table pointer, then
1112 /// retires the old table. The old table's destructor frees whatever
1113 /// nodes remain in its chains at reclamation time — entries removed or
1114 /// replaced in the meantime were unlinked and retired individually, so
1115 /// nothing is freed twice and nothing leaks.
1116 fn try_resize(&self, new_capacity: usize) {
1117 if self
1118 .resizing
1119 .compare_exchange(false, true, Ordering::SeqCst, Ordering::Relaxed)
1120 .is_err()
1121 {
1122 return;
1123 }
1124
1125 // Dekker/SB fence: pairs with the matching fence every writer
1126 // (insert/insert_if_absent/remove) executes right before its
1127 // resizing/table re-validation check. This claim-CAS and the
1128 // migration sweep's bucket reads below are this thread's
1129 // store-then-load half of the same store-load race a writer's
1130 // data-mutating CAS and its own re-validation load form the other
1131 // half of; without a fence on both sides, AcqRel/Acquire permits
1132 // both this sweep and that writer's check to observe the pre-update
1133 // value of the other's write (the classic store-buffering litmus
1134 // test), silently losing the writer's update to the table being
1135 // retired. x86 TSO hides the gap (every CAS there is already a full
1136 // fence); ARM's AcqRel is not.
1137 fence(Ordering::SeqCst);
1138
1139 let new_capacity = new_capacity
1140 .next_power_of_two()
1141 .max(MIN_CAPACITY)
1142 .max(self.floor);
1143 let guard = pin();
1144 let old_raw = self.table.load(Ordering::Acquire, &guard).as_raw();
1145 let old_table = TableRef::<K, V>::from_raw(old_raw);
1146
1147 if old_table.capacity() == new_capacity {
1148 self.resizing.store(false, Ordering::Release);
1149 return;
1150 }
1151
1152 let new_table = TableRef::<K, V>::alloc(new_capacity);
1153
1154 // Migrate: clone every live entry (logically-deleted nodes — tagged
1155 // next — are skipped). We are the only writer of the new table (it
1156 // is unpublished), so plain stores are sufficient.
1157 for i in 0..old_table.capacity() {
1158 let bucket = old_table.bucket(i);
1159 let mut current = bucket.load(Ordering::Acquire, &guard).as_raw();
1160 while !current.is_null() {
1161 let node = unsafe { &*current };
1162 let next = node.next.load(Ordering::Acquire, &guard).as_raw();
1163 if !is_tagged(next) {
1164 let dst = new_table.bucket(new_table.bucket_index(node.hash));
1165 let head = dst.load(Ordering::Relaxed, &guard);
1166 let clone = Box::into_raw(Box::new(Node {
1167 retired: RetiredNode::new(),
1168 hash: node.hash,
1169 key: node.key.clone(),
1170 value: node.value.clone(),
1171 next: Atomic::new(head.as_raw()),
1172 }));
1173 dst.store(unsafe { Shared::from_raw(clone) }, Ordering::Relaxed);
1174 }
1175 current = untag(next);
1176 }
1177 }
1178
1179 match self.table.compare_exchange(
1180 unsafe { Shared::from_raw(old_raw) },
1181 unsafe { Shared::from_raw(new_table.as_raw()) },
1182 Ordering::Release,
1183 Ordering::Relaxed,
1184 &guard,
1185 ) {
1186 Ok(_) => {
1187 // Retire the old table through its proxy (built eagerly
1188 // back when this table was allocated, see `TableRef::alloc`,
1189 // so its birth_epoch predates every straggler that could
1190 // have observed this table): reclamation (which frees the
1191 // remaining chains and the allocation) is deferred until
1192 // every guard that could observe it is gone.
1193 let proxy = old_table.take_proxy();
1194 // SAFETY: TableProxy is #[repr(C)] with RetiredNode at
1195 // offset 0, allocated via Box::into_raw.
1196 unsafe { retire(proxy) };
1197 }
1198 Err(_) => {
1199 // Table changed under us (cannot normally happen — we hold
1200 // the resize latch). Discard the unpublished new table.
1201 unsafe { new_table.free() };
1202 }
1203 }
1204
1205 self.resizing.store(false, Ordering::Release);
1206 }
1207
1208 /// Returns an iterator over the map entries.
1209 /// Yields (K, V) clones from a table snapshot taken at creation.
1210 pub fn iter(&self) -> Iter<'_, K, V, S> {
1211 let guard = pin();
1212 let table = TableRef::<K, V>::from_raw(self.table.load(Ordering::Acquire, &guard).as_raw());
1213 Iter {
1214 _map: self,
1215 table,
1216 bucket_idx: 0,
1217 current: core::ptr::null(),
1218 guard,
1219 }
1220 }
1221
1222 /// Returns an iterator over the map keys.
1223 /// Yields K clones.
1224 pub fn keys(&self) -> Keys<'_, K, V, S> {
1225 Keys { iter: self.iter() }
1226 }
1227
1228 /// Returns an iterator over the map values (clones `V`).
1229 pub fn values(&self) -> Values<'_, K, V, S> {
1230 Values { iter: self.iter() }
1231 }
1232
1233 /// Insert all `(K, V)` pairs from `iter`. Takes `&self` (concurrent map).
1234 pub fn extend<I: IntoIterator<Item = (K, V)>>(&self, iter: I) {
1235 for (k, v) in iter {
1236 self.insert(k, v);
1237 }
1238 }
1239
1240 /// Get the underlying hasher itself.
1241 pub fn hasher(&self) -> &S {
1242 &self.hasher
1243 }
1244}
1245
1246/// Iterator over HashMap entries.
1247///
1248/// Field ordering matters for drop safety.
1249/// Rust drops struct fields in declaration order.
1250/// The `guard` must be dropped *after* `current`/`table` so that the epoch
1251/// pin covering the snapshot is not released before we're done with the raw
1252/// pointers.
1253pub struct Iter<'a, K: 'static, V: 'static, S> {
1254 _map: &'a HashMap<K, V, S>,
1255 table: TableRef<K, V>,
1256 bucket_idx: usize,
1257 current: *const Node<K, V>,
1258 guard: kovan::Guard,
1259}
1260
1261impl<'a, K, V, S> Iterator for Iter<'a, K, V, S>
1262where
1263 K: Clone,
1264 V: Clone,
1265{
1266 type Item = (K, V);
1267
1268 fn next(&mut self) -> Option<Self::Item> {
1269 loop {
1270 if !self.current.is_null() {
1271 unsafe {
1272 let node = &*self.current;
1273 let next = node.next.load(Ordering::Acquire, &self.guard).as_raw();
1274 // Advance current (the pointer may carry a deletion tag).
1275 self.current = untag(next);
1276 if is_tagged(next) {
1277 // Logically deleted — do not yield.
1278 continue;
1279 }
1280 return Some((node.key.clone(), node.value.clone()));
1281 }
1282 }
1283
1284 // Move to next bucket
1285 let table = self.table;
1286 if self.bucket_idx >= table.capacity() {
1287 return None;
1288 }
1289
1290 let bucket = table.bucket(self.bucket_idx);
1291 self.bucket_idx += 1;
1292 self.current = bucket.load(Ordering::Acquire, &self.guard).as_raw();
1293 }
1294 }
1295}
1296
1297/// Iterator over HashMap keys.
1298pub struct Keys<'a, K: 'static, V: 'static, S> {
1299 iter: Iter<'a, K, V, S>,
1300}
1301
1302impl<'a, K, V, S> Iterator for Keys<'a, K, V, S>
1303where
1304 K: Clone,
1305 V: Clone,
1306{
1307 type Item = K;
1308
1309 fn next(&mut self) -> Option<Self::Item> {
1310 self.iter.next().map(|(k, _)| k)
1311 }
1312}
1313
1314impl<'a, K, V, S> IntoIterator for &'a HashMap<K, V, S>
1315where
1316 K: Hash + Eq + Clone + 'static,
1317 V: Clone + 'static,
1318 S: BuildHasher,
1319{
1320 type Item = (K, V);
1321 type IntoIter = Iter<'a, K, V, S>;
1322
1323 fn into_iter(self) -> Self::IntoIter {
1324 self.iter()
1325 }
1326}
1327
1328/// Iterator over HashMap values (clones `V`).
1329pub struct Values<'a, K: 'static, V: 'static, S> {
1330 iter: Iter<'a, K, V, S>,
1331}
1332
1333impl<'a, K, V, S> Iterator for Values<'a, K, V, S>
1334where
1335 K: Clone,
1336 V: Clone,
1337{
1338 type Item = V;
1339
1340 #[inline]
1341 fn next(&mut self) -> Option<V> {
1342 self.iter.next().map(|(_, v)| v)
1343 }
1344}
1345
1346/// Owned iterator yielding `(K, V)` by value — moves out of the nodes, no
1347/// clone. Consuming the map gives exclusive access, so no guard protection of
1348/// the yielded values is needed.
1349pub struct IntoIter<K: 'static, V: 'static> {
1350 table: TableRef<K, V>,
1351 bucket_idx: usize,
1352 current: *mut Node<K, V>,
1353 guard: kovan::Guard,
1354}
1355
1356impl<K, V> Iterator for IntoIter<K, V> {
1357 type Item = (K, V);
1358
1359 fn next(&mut self) -> Option<(K, V)> {
1360 loop {
1361 if !self.current.is_null() {
1362 let node = self.current;
1363 let next = unsafe { (*node).next.load(Ordering::Acquire, &self.guard).as_raw() };
1364 self.current = untag(next);
1365 if is_tagged(next) {
1366 continue; // logically deleted, owned by kovan
1367 }
1368 // Move K and V out, then free the shell without running drop.
1369 let k = unsafe { core::ptr::read(&(*node).key) };
1370 let v = unsafe { core::ptr::read(&(*node).value) };
1371 unsafe {
1372 alloc::alloc::dealloc(
1373 node as *mut u8,
1374 core::alloc::Layout::new::<Node<K, V>>(),
1375 );
1376 }
1377 return Some((k, v));
1378 }
1379 if self.bucket_idx >= self.table.capacity() {
1380 return None;
1381 }
1382 let bucket = self.table.bucket(self.bucket_idx);
1383 self.bucket_idx += 1;
1384 self.current = bucket.load(Ordering::Acquire, &self.guard).as_raw();
1385 }
1386 }
1387}
1388
1389impl<K, V> Drop for IntoIter<K, V> {
1390 fn drop(&mut self) {
1391 while self.next().is_some() {} // drop remaining live K/V + free shells
1392 unsafe { self.table.free_array_only() };
1393 }
1394}
1395
1396impl<K, V, S> IntoIterator for HashMap<K, V, S>
1397where
1398 K: 'static,
1399 V: 'static,
1400{
1401 type Item = (K, V);
1402 type IntoIter = IntoIter<K, V>;
1403
1404 fn into_iter(self) -> IntoIter<K, V> {
1405 let mut me = core::mem::ManuallyDrop::new(self);
1406 let guard = pin();
1407 let table = TableRef::<K, V>::from_raw(me.table.load(Ordering::Relaxed, &guard).as_raw());
1408 // Suppress HashMap::drop (we own the table now); drop only the hasher —
1409 // the remaining fields are atomics / usize / ZST marker.
1410 unsafe { core::ptr::drop_in_place(&mut me.hasher) };
1411 IntoIter {
1412 table,
1413 bucket_idx: 0,
1414 current: core::ptr::null_mut(),
1415 guard,
1416 }
1417 }
1418}
1419
1420impl<K, V, S> core::iter::FromIterator<(K, V)> for HashMap<K, V, S>
1421where
1422 K: Hash + Eq + Clone + Send + 'static,
1423 V: Clone + Send + 'static,
1424 S: BuildHasher + Default,
1425{
1426 fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
1427 let map = Self::with_capacity_and_hasher(MIN_CAPACITY, S::default());
1428 for (k, v) in iter {
1429 map.insert(k, v);
1430 }
1431 map
1432 }
1433}
1434
1435#[cfg(feature = "std")]
1436impl<K, V> Default for HashMap<K, V, FixedState>
1437where
1438 K: Hash + Eq + Clone + 'static,
1439 V: Clone + 'static,
1440{
1441 fn default() -> Self {
1442 Self::new()
1443 }
1444}
1445
1446// SAFETY: HashMap is Send if K, V, S are Send (moving ownership between threads).
1447// HashMap is Sync if K, V, S are Send+Sync. The stronger bound on Sync is needed
1448// because concurrent `get()` calls clone V through a `&V` reference across threads;
1449// if V were Send but not Sync, sharing `&HashMap` could transmit a non-Sync V reference
1450// to another thread via clone(), violating thread-safety.
1451unsafe impl<K: Send, V: Send, S: Send> Send for HashMap<K, V, S> {}
1452unsafe impl<K: Send + Sync, V: Send + Sync, S: Send + Sync> Sync for HashMap<K, V, S> {}
1453
1454impl<K: 'static, V: 'static, S> Drop for HashMap<K, V, S> {
1455 fn drop(&mut self) {
1456 // SAFETY: `drop(&mut self)` guarantees exclusive ownership — no concurrent
1457 // readers can exist. Rust's type system enforces this: `Iter<'a, …>` borrows
1458 // `&'a HashMap`, so it cannot outlive the `HashMap`. The Table's destructor
1459 // frees its chains.
1460 let guard = pin();
1461 let table = TableRef::<K, V>::from_raw(self.table.load(Ordering::Relaxed, &guard).as_raw());
1462 drop(guard);
1463 // SAFETY: exclusive access; frees remaining chains + the allocation.
1464 unsafe { table.free() };
1465
1466 // Flush nodes/tables previously retired by concurrent operations
1467 kovan::flush();
1468 }
1469}
1470
1471#[cfg(test)]
1472mod tests {
1473 use super::*;
1474
1475 #[test]
1476 fn test_insert_and_get() {
1477 let map = HashMap::new();
1478 assert_eq!(map.insert(1, 100), None);
1479 assert_eq!(map.get(&1), Some(100));
1480 assert_eq!(map.get(&2), None);
1481 }
1482
1483 #[test]
1484 fn test_insert_replace() {
1485 let map = HashMap::new();
1486 assert_eq!(map.insert(1, 100), None);
1487 assert_eq!(map.insert(1, 200), Some(100));
1488 assert_eq!(map.get(&1), Some(200));
1489 }
1490
1491 #[test]
1492 fn test_grow() {
1493 let map = HashMap::with_capacity(64);
1494 assert_eq!(map.capacity(), 64);
1495 for i in 0..1000u64 {
1496 map.insert(i, i * 2);
1497 }
1498 assert!(map.capacity() > 64, "map should have grown");
1499 for i in 0..1000u64 {
1500 assert_eq!(map.get(&i), Some(i * 2));
1501 }
1502 assert_eq!(map.len(), 1000);
1503 }
1504
1505 #[test]
1506 fn test_shrink() {
1507 let map = HashMap::with_capacity(64);
1508 for i in 0..1000u64 {
1509 map.insert(i, i);
1510 }
1511 let grown = map.capacity();
1512 assert!(grown > 64);
1513 for i in 0..1000u64 {
1514 map.remove(&i);
1515 }
1516 assert!(
1517 map.capacity() < grown,
1518 "map should have shrunk (capacity {} -> {})",
1519 grown,
1520 map.capacity()
1521 );
1522 assert!(map.capacity() >= 64, "never below the initial capacity");
1523 assert_eq!(map.len(), 0);
1524 }
1525
1526 #[test]
1527 fn test_no_shrink_below_floor() {
1528 let map = HashMap::with_capacity(4096);
1529 for i in 0..100u64 {
1530 map.insert(i, i);
1531 }
1532 for i in 0..100u64 {
1533 map.remove(&i);
1534 }
1535 assert_eq!(map.capacity(), 4096, "floor preserves sizing intent");
1536 }
1537
1538 #[test]
1539 fn test_concurrent_inserts() {
1540 use alloc::sync::Arc;
1541 extern crate std;
1542 use std::thread;
1543
1544 let map = Arc::new(HashMap::new());
1545 let mut handles = alloc::vec::Vec::new();
1546
1547 for thread_id in 0..4 {
1548 let map_clone = Arc::clone(&map);
1549 let handle = thread::spawn(move || {
1550 for i in 0..1000 {
1551 let key = thread_id * 1000 + i;
1552 map_clone.insert(key, key * 2);
1553 }
1554 });
1555 handles.push(handle);
1556 }
1557
1558 for handle in handles {
1559 handle.join().unwrap();
1560 }
1561
1562 for thread_id in 0..4 {
1563 for i in 0..1000 {
1564 let key = thread_id * 1000 + i;
1565 assert_eq!(map.get(&key), Some(key * 2));
1566 }
1567 }
1568 }
1569
1570 #[test]
1571 fn test_concurrent_grow() {
1572 use alloc::sync::Arc;
1573 extern crate std;
1574 use std::thread;
1575
1576 let map = Arc::new(HashMap::with_capacity(64));
1577 let mut handles = alloc::vec::Vec::new();
1578
1579 for thread_id in 0..8u64 {
1580 let map_clone = Arc::clone(&map);
1581 handles.push(thread::spawn(move || {
1582 for i in 0..2000u64 {
1583 let key = thread_id * 10_000 + i;
1584 map_clone.insert(key, key);
1585 }
1586 }));
1587 }
1588 for handle in handles {
1589 handle.join().unwrap();
1590 }
1591
1592 for thread_id in 0..8u64 {
1593 for i in 0..2000u64 {
1594 let key = thread_id * 10_000 + i;
1595 assert_eq!(map.get(&key), Some(key), "lost key {key} during growth");
1596 }
1597 }
1598 assert!(map.capacity() >= 16_000);
1599 }
1600}