1use super::heap_header::{HeapHeader, HEAP_KIND_V2_TYPED_MAP};
13use std::alloc::{Layout, alloc_zeroed, dealloc};
14
15const HASH_EMPTY: u64 = 0;
17const HASH_TOMBSTONE: u64 = 1;
18
19const HASH_MIN_OCCUPIED: u64 = 2;
21
22const LOAD_FACTOR_NUM: u32 = 3;
24const LOAD_FACTOR_DEN: u32 = 4;
25
26const INITIAL_CAPACITY: u32 = 8;
28
29#[repr(C)]
31pub struct Bucket<K, V> {
32 pub hash: u64,
34 pub key: K,
35 pub value: V,
36}
37
38#[repr(C)]
40pub struct TypedMap<K, V> {
41 pub header: HeapHeader,
42 pub buckets: *mut Bucket<K, V>,
44 pub bucket_count: u32,
46 pub len: u32,
48 pub tombstone_count: u32,
50 pub _pad: u32,
51}
52
53pub type TypedMapStringF64 = TypedMap<*const u8, f64>;
55pub type TypedMapStringI64 = TypedMap<*const u8, i64>;
56pub type TypedMapStringPtr = TypedMap<*const u8, *const u8>;
57
58pub type TypedMapI64F64 = TypedMap<i64, f64>;
60pub type TypedMapI64I64 = TypedMap<i64, i64>;
61pub type TypedMapI64Ptr = TypedMap<i64, *const u8>;
62
63#[inline]
65fn fnv1a_hash(bytes: &[u8]) -> u64 {
66 let mut h: u64 = 0xcbf29ce484222325;
67 for &b in bytes {
68 h ^= b as u64;
69 h = h.wrapping_mul(0x100000001b3);
70 }
71 h
72}
73
74#[inline]
77fn fix_hash(h: u64) -> u64 {
78 if h < HASH_MIN_OCCUPIED {
79 h.wrapping_add(HASH_MIN_OCCUPIED)
80 } else {
81 h
82 }
83}
84
85impl<K: Copy, V: Copy> TypedMap<K, V> {
86 pub fn new() -> *mut Self {
88 let layout = Layout::new::<Self>();
89 let ptr = unsafe { alloc_zeroed(layout) as *mut Self };
90 assert!(!ptr.is_null(), "allocation failed for TypedMap");
91
92 unsafe {
93 (*ptr).header = HeapHeader::new(HEAP_KIND_V2_TYPED_MAP);
94 (*ptr).buckets = std::ptr::null_mut();
95 (*ptr).bucket_count = 0;
96 (*ptr).len = 0;
97 (*ptr).tombstone_count = 0;
98 (*ptr)._pad = 0;
99 }
100 ptr
101 }
102
103 #[inline]
108 pub unsafe fn len(this: *const Self) -> u32 {
109 unsafe { (*this).len }
110 }
111
112 #[inline]
117 pub unsafe fn is_empty(this: *const Self) -> bool {
118 unsafe { (*this).len == 0 }
119 }
120
121 pub unsafe fn drop_map(ptr: *mut Self) {
126 unsafe {
127 let map = &*ptr;
128 if map.bucket_count > 0 && !map.buckets.is_null() {
129 let bucket_layout = Layout::array::<Bucket<K, V>>(map.bucket_count as usize)
130 .expect("invalid bucket layout");
131 dealloc(map.buckets as *mut u8, bucket_layout);
132 }
133 let layout = Layout::new::<Self>();
134 dealloc(ptr as *mut u8, layout);
135 }
136 }
137
138 fn alloc_buckets(count: u32) -> *mut Bucket<K, V> {
140 let layout =
141 Layout::array::<Bucket<K, V>>(count as usize).expect("invalid bucket layout");
142 let ptr = unsafe { alloc_zeroed(layout) as *mut Bucket<K, V> };
143 assert!(!ptr.is_null(), "allocation failed for TypedMap buckets");
144 ptr
145 }
146
147 unsafe fn ensure_capacity(this: *mut Self) {
152 unsafe {
153 let map = &*this;
154 if map.bucket_count == 0 {
155 (*this).buckets = Self::alloc_buckets(INITIAL_CAPACITY);
156 (*this).bucket_count = INITIAL_CAPACITY;
157 return;
158 }
159 let used = map.len + map.tombstone_count;
161 if used * LOAD_FACTOR_DEN >= map.bucket_count * LOAD_FACTOR_NUM {
162 Self::grow(this);
163 }
164 }
165 }
166
167 unsafe fn grow(this: *mut Self) {
172 unsafe {
173 let map = &*this;
174 let old_buckets = map.buckets;
175 let old_count = map.bucket_count;
176
177 let new_count = old_count * 2;
178 let new_buckets = Self::alloc_buckets(new_count);
179 let mask = new_count - 1; for i in 0..old_count {
183 let bucket = &*old_buckets.add(i as usize);
184 if bucket.hash >= HASH_MIN_OCCUPIED {
185 let mut idx = (bucket.hash as u32) & mask;
186 loop {
187 let dst = &*new_buckets.add(idx as usize);
188 if dst.hash == HASH_EMPTY {
189 std::ptr::write(
190 new_buckets.add(idx as usize),
191 Bucket {
192 hash: bucket.hash,
193 key: bucket.key,
194 value: bucket.value,
195 },
196 );
197 break;
198 }
199 idx = (idx + 1) & mask;
200 }
201 }
202 }
203
204 let old_layout = Layout::array::<Bucket<K, V>>(old_count as usize)
206 .expect("invalid bucket layout");
207 dealloc(old_buckets as *mut u8, old_layout);
208
209 (*this).buckets = new_buckets;
210 (*this).bucket_count = new_count;
211 (*this).tombstone_count = 0;
212 }
213 }
214}
215
216use super::string_obj::StringObj;
222
223impl<V: Copy> TypedMap<*const u8, V> {
224 pub unsafe fn insert(this: *mut Self, key: *const StringObj, value: V) -> Option<V> {
233 unsafe {
234 Self::ensure_capacity(this);
235
236 let key_str = StringObj::as_str(key);
237 let hash = fix_hash(fnv1a_hash(key_str.as_bytes()));
238 let map = &*this;
239 let mask = map.bucket_count - 1;
240 let mut idx = (hash as u32) & mask;
241 let mut first_tombstone: Option<u32> = None;
242
243 loop {
244 let bucket = &*map.buckets.add(idx as usize);
245 match bucket.hash {
246 HASH_EMPTY => {
247 let insert_idx = first_tombstone.unwrap_or(idx);
249 if first_tombstone.is_some() {
250 (*this).tombstone_count -= 1;
251 }
252 std::ptr::write(
253 (*this).buckets.add(insert_idx as usize),
254 Bucket {
255 hash,
256 key: key as *const u8,
257 value,
258 },
259 );
260 (*this).len += 1;
261 return None;
262 }
263 HASH_TOMBSTONE => {
264 if first_tombstone.is_none() {
265 first_tombstone = Some(idx);
266 }
267 }
268 h if h == hash => {
269 let existing_key = bucket.key as *const StringObj;
271 let existing_str = StringObj::as_str(existing_key);
272 if existing_str == key_str {
273 let old = bucket.value;
275 (*(*this).buckets.add(idx as usize)).value = value;
276 return Some(old);
277 }
278 }
279 _ => {}
280 }
281 idx = (idx + 1) & mask;
282 }
283 }
284 }
285
286 pub unsafe fn get(this: *const Self, key: *const StringObj) -> Option<V> {
291 unsafe {
292 let map = &*this;
293 if map.len == 0 || map.bucket_count == 0 {
294 return None;
295 }
296
297 let key_str = StringObj::as_str(key);
298 let hash = fix_hash(fnv1a_hash(key_str.as_bytes()));
299 let mask = map.bucket_count - 1;
300 let mut idx = (hash as u32) & mask;
301
302 loop {
303 let bucket = &*map.buckets.add(idx as usize);
304 match bucket.hash {
305 HASH_EMPTY => return None,
306 HASH_TOMBSTONE => {}
307 h if h == hash => {
308 let existing_key = bucket.key as *const StringObj;
309 let existing_str = StringObj::as_str(existing_key);
310 if existing_str == key_str {
311 return Some(bucket.value);
312 }
313 }
314 _ => {}
315 }
316 idx = (idx + 1) & mask;
317 }
318 }
319 }
320
321 pub unsafe fn contains_key(this: *const Self, key: *const StringObj) -> bool {
326 unsafe { Self::get(this, key).is_some() }
327 }
328
329 pub unsafe fn remove(this: *mut Self, key: *const StringObj) -> Option<V> {
334 unsafe {
335 let map = &*this;
336 if map.len == 0 || map.bucket_count == 0 {
337 return None;
338 }
339
340 let key_str = StringObj::as_str(key);
341 let hash = fix_hash(fnv1a_hash(key_str.as_bytes()));
342 let mask = map.bucket_count - 1;
343 let mut idx = (hash as u32) & mask;
344
345 loop {
346 let bucket = &*map.buckets.add(idx as usize);
347 match bucket.hash {
348 HASH_EMPTY => return None,
349 HASH_TOMBSTONE => {}
350 h if h == hash => {
351 let existing_key = bucket.key as *const StringObj;
352 let existing_str = StringObj::as_str(existing_key);
353 if existing_str == key_str {
354 let old_value = bucket.value;
355 (*(*this).buckets.add(idx as usize)).hash = HASH_TOMBSTONE;
357 (*this).len -= 1;
358 (*this).tombstone_count += 1;
359 return Some(old_value);
360 }
361 }
362 _ => {}
363 }
364 idx = (idx + 1) & mask;
365 }
366 }
367 }
368}
369
370impl<V: Copy> TypedMap<i64, V> {
377 pub unsafe fn insert_i64(this: *mut Self, key: i64, value: V) -> Option<V> {
383 unsafe {
384 Self::ensure_capacity(this);
385
386 let hash = fix_hash(fnv1a_hash(&key.to_le_bytes()));
387 let map = &*this;
388 let mask = map.bucket_count - 1;
389 let mut idx = (hash as u32) & mask;
390 let mut first_tombstone: Option<u32> = None;
391
392 loop {
393 let bucket = &*map.buckets.add(idx as usize);
394 match bucket.hash {
395 HASH_EMPTY => {
396 let insert_idx = first_tombstone.unwrap_or(idx);
397 if first_tombstone.is_some() {
398 (*this).tombstone_count -= 1;
399 }
400 std::ptr::write(
401 (*this).buckets.add(insert_idx as usize),
402 Bucket { hash, key, value },
403 );
404 (*this).len += 1;
405 return None;
406 }
407 HASH_TOMBSTONE => {
408 if first_tombstone.is_none() {
409 first_tombstone = Some(idx);
410 }
411 }
412 h if h == hash => {
413 if bucket.key == key {
414 let old = bucket.value;
415 (*(*this).buckets.add(idx as usize)).value = value;
416 return Some(old);
417 }
418 }
419 _ => {}
420 }
421 idx = (idx + 1) & mask;
422 }
423 }
424 }
425
426 pub unsafe fn get_i64(this: *const Self, key: i64) -> Option<V> {
431 unsafe {
432 let map = &*this;
433 if map.len == 0 || map.bucket_count == 0 {
434 return None;
435 }
436
437 let hash = fix_hash(fnv1a_hash(&key.to_le_bytes()));
438 let mask = map.bucket_count - 1;
439 let mut idx = (hash as u32) & mask;
440
441 loop {
442 let bucket = &*map.buckets.add(idx as usize);
443 match bucket.hash {
444 HASH_EMPTY => return None,
445 HASH_TOMBSTONE => {}
446 h if h == hash => {
447 if bucket.key == key {
448 return Some(bucket.value);
449 }
450 }
451 _ => {}
452 }
453 idx = (idx + 1) & mask;
454 }
455 }
456 }
457
458 pub unsafe fn contains_key_i64(this: *const Self, key: i64) -> bool {
463 unsafe { Self::get_i64(this, key).is_some() }
464 }
465
466 pub unsafe fn remove_i64(this: *mut Self, key: i64) -> Option<V> {
471 unsafe {
472 let map = &*this;
473 if map.len == 0 || map.bucket_count == 0 {
474 return None;
475 }
476
477 let hash = fix_hash(fnv1a_hash(&key.to_le_bytes()));
478 let mask = map.bucket_count - 1;
479 let mut idx = (hash as u32) & mask;
480
481 loop {
482 let bucket = &*map.buckets.add(idx as usize);
483 match bucket.hash {
484 HASH_EMPTY => return None,
485 HASH_TOMBSTONE => {}
486 h if h == hash => {
487 if bucket.key == key {
488 let old_value = bucket.value;
489 (*(*this).buckets.add(idx as usize)).hash = HASH_TOMBSTONE;
490 (*this).len -= 1;
491 (*this).tombstone_count += 1;
492 return Some(old_value);
493 }
494 }
495 _ => {}
496 }
497 idx = (idx + 1) & mask;
498 }
499 }
500 }
501}
502
503#[cfg(test)]
504mod tests {
505 use super::*;
506
507 fn make_key(s: &str) -> *mut StringObj {
509 StringObj::new(s)
510 }
511
512 #[test]
513 fn test_new_empty_map() {
514 let map = TypedMapStringF64::new();
515 unsafe {
516 assert_eq!(TypedMap::len(map), 0);
517 assert!(TypedMap::is_empty(map));
518 assert_eq!((*map).header.kind(), HEAP_KIND_V2_TYPED_MAP);
519 assert_eq!((*map).header.get_refcount(), 1);
520 TypedMap::drop_map(map);
521 }
522 }
523
524 #[test]
525 fn test_insert_and_get() {
526 let map = TypedMapStringF64::new();
527 let k1 = make_key("x");
528 let k2 = make_key("y");
529 let k3 = make_key("z");
530
531 unsafe {
532 assert_eq!(TypedMap::insert(map, k1, 1.0), None);
533 assert_eq!(TypedMap::insert(map, k2, 2.0), None);
534 assert_eq!(TypedMap::insert(map, k3, 3.0), None);
535 assert_eq!(TypedMap::len(map), 3);
536
537 let lookup_x = make_key("x");
539 let lookup_y = make_key("y");
540 let lookup_z = make_key("z");
541
542 assert_eq!(TypedMap::get(map, lookup_x), Some(1.0));
543 assert_eq!(TypedMap::get(map, lookup_y), Some(2.0));
544 assert_eq!(TypedMap::get(map, lookup_z), Some(3.0));
545
546 let missing = make_key("missing");
547 assert_eq!(TypedMap::get(map, missing), None);
548
549 StringObj::drop(lookup_x);
550 StringObj::drop(lookup_y);
551 StringObj::drop(lookup_z);
552 StringObj::drop(missing);
553 StringObj::drop(k1);
554 StringObj::drop(k2);
555 StringObj::drop(k3);
556 TypedMap::drop_map(map);
557 }
558 }
559
560 #[test]
561 fn test_insert_update_returns_old_value() {
562 let map = TypedMapStringF64::new();
563 let k1 = make_key("key");
564 let k2 = make_key("key"); unsafe {
567 assert_eq!(TypedMap::insert(map, k1, 1.0), None);
568 assert_eq!(TypedMap::insert(map, k2, 2.0), Some(1.0)); assert_eq!(TypedMap::len(map), 1); let lookup = make_key("key");
572 assert_eq!(TypedMap::get(map, lookup), Some(2.0)); StringObj::drop(lookup);
575 StringObj::drop(k1);
576 StringObj::drop(k2);
577 TypedMap::drop_map(map);
578 }
579 }
580
581 #[test]
582 fn test_contains_key() {
583 let map = TypedMapStringF64::new();
584 let k = make_key("present");
585
586 unsafe {
587 TypedMap::insert(map, k, 42.0);
588
589 let lookup_yes = make_key("present");
590 let lookup_no = make_key("absent");
591 assert!(TypedMap::contains_key(map, lookup_yes));
592 assert!(!TypedMap::contains_key(map, lookup_no));
593
594 StringObj::drop(lookup_yes);
595 StringObj::drop(lookup_no);
596 StringObj::drop(k);
597 TypedMap::drop_map(map);
598 }
599 }
600
601 #[test]
602 fn test_remove() {
603 let map = TypedMapStringI64::new();
604 let k1 = make_key("a");
605 let k2 = make_key("b");
606
607 unsafe {
608 TypedMap::insert(map, k1, 10i64);
609 TypedMap::insert(map, k2, 20i64);
610 assert_eq!(TypedMap::len(map), 2);
611
612 let rm_key = make_key("a");
613 assert_eq!(TypedMap::remove(map, rm_key), Some(10i64));
614 assert_eq!(TypedMap::len(map), 1);
615
616 let lookup = make_key("a");
618 assert_eq!(TypedMap::get(map, lookup), None);
619
620 let lookup_b = make_key("b");
622 assert_eq!(TypedMap::get(map, lookup_b), Some(20i64));
623
624 let rm_missing = make_key("missing");
626 assert_eq!(TypedMap::remove(map, rm_missing), None);
627
628 StringObj::drop(rm_key);
629 StringObj::drop(lookup);
630 StringObj::drop(lookup_b);
631 StringObj::drop(rm_missing);
632 StringObj::drop(k1);
633 StringObj::drop(k2);
634 TypedMap::drop_map(map);
635 }
636 }
637
638 #[test]
639 fn test_grow_and_rehash() {
640 let map = TypedMapStringF64::new();
641 let mut keys = Vec::new();
642
643 unsafe {
644 for i in 0..50 {
646 let key = make_key(&format!("key_{i}"));
647 TypedMap::insert(map, key, i as f64);
648 keys.push(key);
649 }
650
651 assert_eq!(TypedMap::len(map), 50);
652
653 for i in 0..50 {
655 let lookup = make_key(&format!("key_{i}"));
656 assert_eq!(
657 TypedMap::get(map, lookup),
658 Some(i as f64),
659 "missing key_{i} after grow"
660 );
661 StringObj::drop(lookup);
662 }
663
664 for k in keys {
665 StringObj::drop(k);
666 }
667 TypedMap::drop_map(map);
668 }
669 }
670
671 #[test]
672 fn test_collision_handling() {
673 let map = TypedMapStringF64::new();
675 let mut keys = Vec::new();
676
677 unsafe {
678 for i in 0..20 {
679 let key = make_key(&format!("{i}"));
680 TypedMap::insert(map, key, i as f64);
681 keys.push(key);
682 }
683
684 assert_eq!(TypedMap::len(map), 20);
685
686 for i in 0..20 {
687 let lookup = make_key(&format!("{i}"));
688 assert_eq!(TypedMap::get(map, lookup), Some(i as f64));
689 StringObj::drop(lookup);
690 }
691
692 for k in keys {
693 StringObj::drop(k);
694 }
695 TypedMap::drop_map(map);
696 }
697 }
698
699 #[test]
700 fn test_remove_then_insert_reuses_tombstone() {
701 let map = TypedMapStringF64::new();
702
703 unsafe {
704 let k1 = make_key("alpha");
705 TypedMap::insert(map, k1, 1.0);
706
707 let rm = make_key("alpha");
708 TypedMap::remove(map, rm);
709 assert_eq!(TypedMap::len(map), 0);
710 assert_eq!((*map).tombstone_count, 1);
711
712 let k2 = make_key("alpha");
714 TypedMap::insert(map, k2, 2.0);
715 assert_eq!(TypedMap::len(map), 1);
716 assert_eq!((*map).tombstone_count, 0);
717
718 let lookup = make_key("alpha");
719 assert_eq!(TypedMap::get(map, lookup), Some(2.0));
720
721 StringObj::drop(lookup);
722 StringObj::drop(k1);
723 StringObj::drop(k2);
724 StringObj::drop(rm);
725 TypedMap::drop_map(map);
726 }
727 }
728
729 #[test]
730 fn test_get_on_empty_map() {
731 let map = TypedMapStringF64::new();
732 unsafe {
733 let k = make_key("anything");
734 assert_eq!(TypedMap::get(map, k), None);
735 StringObj::drop(k);
736 TypedMap::drop_map(map);
737 }
738 }
739
740 #[test]
741 fn test_string_ptr_map() {
742 let map = TypedMapStringPtr::new();
743 unsafe {
744 let k = make_key("ptr_key");
745 let val_str = make_key("value");
746 TypedMap::insert(map, k, val_str as *const u8);
747
748 let lookup = make_key("ptr_key");
749 let result = TypedMap::get(map, lookup);
750 assert!(result.is_some());
751 let retrieved = result.unwrap() as *const StringObj;
752 assert_eq!(StringObj::as_str(retrieved), "value");
753
754 StringObj::drop(lookup);
755 StringObj::drop(k);
756 StringObj::drop(val_str);
757 TypedMap::drop_map(map);
758 }
759 }
760}