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