twizzler/collections/hachage/raw/
mod.rs

1use std::{
2    alloc::{AllocError, Layout},
3    marker::PhantomData,
4    ptr::NonNull,
5};
6
7use twizzler_abi::object::{MAX_SIZE, NULLPAGE_SIZE};
8use twizzler_derive::Invariant;
9
10use super::DefaultHashBuilder;
11use crate::{
12    alloc::Allocator,
13    collections::hachage::control::{Tag, TagSliceExt},
14    marker::{BaseType, Invariant},
15    ptr::{GlobalPtr, InvPtr, Ref, RefMut, RefSliceMut},
16    Result,
17};
18
19// todo figure out a better way to parameterize size
20// generic feels too clunky, so does a const usize
21#[derive(Default, Clone, Copy)]
22pub struct HashTableAlloc {}
23
24const MAX_ALIGNMENT: usize = 4096;
25// We need some leeway for aligning the data
26const MAX_SPACE: usize = MAX_SIZE - NULLPAGE_SIZE * 4 - MAX_ALIGNMENT;
27
28// We need to have a set group width since invariant pointers mean that computers that use SSE2 or
29// AVX512 can interpret the same hash table. 64 bytes is a good middle ground.
30const INVARIANT_GROUP_WIDTH: usize = 64;
31
32// This is a bit of a hack, but this assumes that the hash table is on a single object with no other
33// allocations on the object. So we know the size of the tag, and the layout provides information
34// on the data. However we do need to store the size, since Layout shows the total space of the
35// allocation and not just the size of data. Also we can't just feed the Layout for the data array
36// because we also want the hashtable to use other traditional allocators. Though this means this
37// yields a pointer that overlaps with a previous allocation.
38
39impl Allocator for HashTableAlloc {
40    fn alloc(
41        &self,
42        _layout: Layout,
43    ) -> std::result::Result<crate::ptr::GlobalPtr<u8>, std::alloc::AllocError> {
44        panic!("unsupported")
45    }
46
47    unsafe fn dealloc(&self, _ptr: crate::ptr::GlobalPtr<u8>, _layout: Layout) {}
48
49    unsafe fn realloc(
50        &self,
51        _ptr: GlobalPtr<u8>,
52        _layout: Layout,
53        _newsize: usize,
54    ) -> std::result::Result<GlobalPtr<u8>, AllocError> {
55        panic!("unsupported")
56    }
57}
58
59impl HashTableAlloc {
60    // This will return a pointer to the middle of the allocation, specifically the ptr right after
61    // the data portion or the first Tag
62    fn allocate(
63        &self,
64        table_layout: &TableLayout,
65        buckets: usize,
66    ) -> std::result::Result<crate::ptr::GlobalPtr<u8>, std::alloc::AllocError> {
67        assert!(buckets.is_power_of_two());
68        let (layout, _) = table_layout
69            .calculate_layout_for(buckets)
70            .ok_or(std::alloc::AllocError)?;
71        if layout.align() > MAX_ALIGNMENT {
72            return Err(std::alloc::AllocError);
73        }
74        // 1 for null page, 2 for metadata pages, 1 for base
75        if layout.size() > MAX_SPACE {
76            return Err(std::alloc::AllocError);
77        }
78        let offset = (MAX_SPACE / (table_layout.size + 1)) * table_layout.size;
79        let corrected_offset = offset + (MAX_ALIGNMENT - offset % MAX_ALIGNMENT);
80        let obj = twizzler_rt_abi::object::twz_rt_get_object_handle((self as *const Self).cast())
81            .unwrap();
82
83        Ok(GlobalPtr::new(
84            obj.id(),
85            (NULLPAGE_SIZE * 2 + corrected_offset) as u64,
86        ))
87    }
88}
89
90#[inline]
91#[allow(clippy::cast_possible_truncation)]
92fn h1(hash: u64) -> usize {
93    // On 32-bit platforms we simply ignore the higher hash bits.
94    hash as usize
95}
96
97#[derive(Copy, Clone)]
98struct TableLayout {
99    pub size: usize,
100    pub ctrl_align: usize,
101}
102
103impl TableLayout {
104    #[inline]
105    const fn new<T: Invariant>() -> Self {
106        let layout = Layout::new::<T>();
107        Self {
108            size: layout.size(),
109            ctrl_align: if layout.align() > INVARIANT_GROUP_WIDTH {
110                layout.align()
111            } else {
112                INVARIANT_GROUP_WIDTH
113            },
114        }
115    }
116
117    fn calculate_ctrl_offset(&self, buckets: usize) -> usize {
118        self.size * buckets + (self.ctrl_align - 1) & !(self.ctrl_align - 1)
119    }
120
121    #[inline]
122    fn calculate_layout_for(&self, buckets: usize) -> Option<(Layout, usize)> {
123        debug_assert!(buckets.is_power_of_two());
124
125        // TODO: fix this for regular allocators and do bounds checking
126        // maybe enforce a minimum element count?
127        // Well I guess we can just check it twice maybe.
128        // This calculates the nearest alignment value for buckets * self.size
129        let ctrl_offset = self.calculate_ctrl_offset(buckets);
130        let len = ctrl_offset + buckets + INVARIANT_GROUP_WIDTH;
131
132        Some((
133            unsafe { Layout::from_size_align_unchecked(len, self.ctrl_align) },
134            ctrl_offset,
135        ))
136    }
137}
138
139struct ProbeSeq {
140    pos: usize,
141}
142
143impl ProbeSeq {
144    fn move_next(&mut self, bucket_mask: usize) {
145        self.pos += 1;
146        self.pos &= bucket_mask
147    }
148}
149
150/// Returns the maximum effective capacity for the given bucket mask, taking
151/// the maximum load factor into account.
152#[inline]
153fn bucket_mask_to_capacity(bucket_mask: usize) -> usize {
154    if bucket_mask < 8 {
155        // For tables with 1/2/4/8 buckets, we always reserve one empty slot.
156        // Keep in mind that the bucket mask is one less than the bucket count.
157        bucket_mask
158    } else {
159        // For larger tables we reserve 12.5% of the slots as empty.
160        ((bucket_mask + 1) / 8) * 7
161    }
162}
163
164// To reduce the number of Ref.resolves() to ideally one per call of RawTable
165pub struct CarryCtx<'a> {
166    backing: Ref<'a, u8>,
167}
168
169pub struct CarryCtxMut<'a> {
170    backing: RefMut<'a, u8>,
171}
172
173pub trait Ctx {
174    fn base(&self) -> *const u8;
175
176    #[inline]
177    fn ctrl<'a>(&self, buckets: usize) -> &'a [Tag] {
178        unsafe { std::slice::from_raw_parts(self.base().cast::<Tag>(), buckets) }
179    }
180
181    #[inline]
182    fn bucket<'a, T>(&self, index: usize) -> &'a T {
183        unsafe { self.base().cast::<T>().sub(index + 1).as_ref_unchecked() }
184    }
185}
186
187pub trait CtxMut: Ctx {
188    fn base_mut(&self) -> *mut u8;
189
190    #[inline]
191    fn ctrl_mut(&self, buckets: usize) -> &mut [Tag] {
192        unsafe { std::slice::from_raw_parts_mut(self.base_mut().cast(), buckets) }
193    }
194
195    #[inline]
196    fn bucket_ptr(&self, index: usize, size_of: usize) -> *mut u8 {
197        unsafe { self.base_mut().sub(size_of * (index + 1)) }
198    }
199
200    fn bucket_ref_mut<T>(&self, index: usize) -> &mut T {
201        unsafe {
202            self.base_mut()
203                .cast::<T>()
204                .sub(index + 1)
205                .as_mut_unchecked()
206        }
207    }
208}
209
210impl CarryCtx<'_> {
211    #[inline]
212    pub fn new(base: Ref<u8>) -> CarryCtx<'_> {
213        CarryCtx { backing: base }
214    }
215}
216
217impl CarryCtxMut<'_> {
218    #[inline]
219    pub fn new(base: RefMut<u8>) -> CarryCtxMut<'_> {
220        CarryCtxMut { backing: base }
221    }
222}
223
224impl Ctx for CarryCtx<'_> {
225    #[inline]
226    fn base(&self) -> *const u8 {
227        self.backing.raw()
228    }
229}
230
231impl Ctx for CarryCtxMut<'_> {
232    #[inline]
233    fn base(&self) -> *const u8 {
234        self.backing.raw().cast_const()
235    }
236}
237
238impl CtxMut for CarryCtxMut<'_> {
239    #[inline]
240    fn base_mut(&self) -> *mut u8 {
241        self.backing.raw()
242    }
243}
244
245#[derive(Invariant)]
246pub struct RawTable<T: Invariant, S = DefaultHashBuilder, A: Allocator = HashTableAlloc> {
247    table: RawTableInner,
248    // I have to keep the hasher state otherwise the hashes won't be the same upon reload
249    hasher: S,
250    alloc: A,
251    _phantom: PhantomData<T>,
252}
253
254impl<T: Invariant, S, A: Allocator> BaseType for RawTable<T, S, A> {}
255
256impl<T: Invariant> RawTable<T, DefaultHashBuilder, HashTableAlloc> {
257    pub fn new() -> Self {
258        Self {
259            table: RawTableInner::new(),
260            hasher: DefaultHashBuilder::default(),
261            alloc: HashTableAlloc::default(),
262            _phantom: PhantomData,
263        }
264    }
265}
266
267impl<T: Invariant, S, A: Allocator> RawTable<T, S, A> {
268    const TABLE_LAYOUT: TableLayout = TableLayout::new::<T>();
269
270    pub fn hasher(&self) -> &S {
271        &self.hasher
272    }
273
274    pub fn allocator(&self) -> &A {
275        &self.alloc
276    }
277
278    pub fn len(&self) -> usize {
279        self.table.items
280    }
281
282    pub fn capacity(&self) -> usize {
283        self.table.growth_left
284    }
285
286    pub fn with_hasher_in(hasher: S, alloc: A) -> Self {
287        Self {
288            table: RawTableInner::new(),
289            hasher,
290            alloc,
291            _phantom: PhantomData,
292        }
293    }
294
295    pub fn remove(
296        &mut self,
297        hash: u64,
298        mut eq: impl FnMut(&T) -> bool,
299        ctx: &impl CtxMut,
300    ) -> Option<T> {
301        match unsafe {
302            self.table
303                .find_inner(hash, &mut |index| eq(&self.bucket(index)), ctx)
304        } {
305            Some(bucket) => Some(unsafe {
306                let item = self.bucket(bucket).raw().read();
307                self.table.erase(bucket);
308                item
309            }),
310            None => None,
311        }
312    }
313
314    pub fn get(&self, hash: u64, mut eq: impl FnMut(&T) -> bool, ctx: &impl Ctx) -> Option<&T> {
315        unsafe {
316            let result = self
317                .table
318                .find_inner(hash, &mut |index| eq(ctx.bucket::<T>(index)), ctx);
319
320            match result {
321                Some(index) => Some(ctx.bucket(index)),
322                None => None,
323            }
324        }
325    }
326
327    pub fn get_mut<'a>(
328        &mut self,
329        hash: u64,
330        mut eq: impl FnMut(&T) -> bool,
331        ctx: &'a impl CtxMut,
332    ) -> Option<&'a mut T> {
333        unsafe {
334            let result = self
335                .table
336                .find_inner(hash, &mut |index| eq(ctx.bucket::<T>(index)), ctx);
337
338            match result {
339                Some(index) => Some(ctx.bucket_ref_mut(index)),
340                None => None,
341            }
342        }
343    }
344
345    pub fn clear(&mut self) {
346        if self.table.is_empty_singleton() {
347            return;
348        }
349        let mut ctrl = unsafe { self.table.ctrl_slice_mut() };
350
351        ctrl.fill_tag(Tag::EMPTY);
352        self.table.items = 0;
353    }
354
355    fn bucket(&self, index: usize) -> Ref<T> {
356        unsafe { self.table.bucket::<T>(index) }
357    }
358
359    pub fn carry_ctx(&self) -> CarryCtx {
360        CarryCtx::new(unsafe { self.table.ctrl.resolve() })
361    }
362
363    pub fn carry_ctx_mut<'a>(&self, base: &RefMut<'a, RawTable<T, S, A>>) -> CarryCtxMut<'a> {
364        CarryCtxMut::new(unsafe { base.table.ctrl.resolve_mut().owned() })
365    }
366
367    pub unsafe fn iter(&self) -> RawIter<T> {
368        self.table.iter()
369    }
370
371    pub unsafe fn backing(&self) -> Ref<u8> {
372        self.table.ctrl.resolve()
373    }
374
375    pub unsafe fn backing_mut(&mut self) -> RefMut<u8> {
376        self.table.ctrl.resolve_mut()
377    }
378}
379
380impl<T: Invariant, S> RawTable<T, S, HashTableAlloc> {
381    pub fn bootstrap(&mut self, capacity: usize) -> Result<()> {
382        unsafe {
383            self.table
384                .bootstrap(&self.alloc, capacity, &Self::TABLE_LAYOUT)
385        }
386    }
387
388    pub fn reserve(&mut self, additional: usize, hasher: impl Fn(&T) -> u64, ctx: &impl CtxMut) {
389        if core::intrinsics::unlikely(additional > self.table.growth_left) {
390            self.reserve_rehash(additional, hasher, ctx).unwrap();
391        }
392    }
393
394    pub fn reserve_rehash(
395        &mut self,
396        additional: usize,
397        hasher: impl Fn(&T) -> u64,
398        ctx: &impl CtxMut,
399    ) -> Result<()> {
400        unsafe {
401            self.table.reserve_rehash_inner(
402                &self.alloc,
403                additional,
404                &|_table, index| hasher(&ctx.bucket(index)),
405                &Self::TABLE_LAYOUT,
406                None,
407                ctx,
408            )
409        }
410    }
411
412    pub unsafe fn resize(
413        &mut self,
414        capacity: usize,
415        hasher: impl Fn(&T) -> u64,
416        ctx: &impl CtxMut,
417    ) -> Result<()> {
418        self.table.resize_inner(
419            &self.alloc,
420            capacity,
421            &|_table, index| hasher(ctx.bucket(index)),
422            &Self::TABLE_LAYOUT,
423            ctx,
424        )
425    }
426
427    // Returns a reference to a slot or a candidate to insert
428    pub fn find_or_find_insert_slot(
429        &mut self,
430        hash: u64,
431        mut eq: impl FnMut(&T) -> bool,
432        hasher: impl Fn(&T) -> u64,
433        ctx: &impl CtxMut,
434    ) -> std::result::Result<Ref<T>, usize> {
435        // self.reserve() can change the invariant pointer (only when the hashtable is empty)
436        self.reserve(1, hasher, ctx);
437
438        unsafe {
439            match self.table.find_or_find_insert_slot_inner(
440                hash,
441                &mut |index| eq(ctx.bucket(index)),
442                ctx,
443            ) {
444                Ok(index) => Ok(self.bucket(index)),
445                Err(slot) => Err(slot),
446            }
447        }
448    }
449
450    #[inline]
451    pub unsafe fn insert_in_slot(&mut self, hash: u64, slot: usize, value: T, ctx: &impl CtxMut) {
452        {
453            let ctrl = ctx.ctrl_mut(self.table.buckets());
454            let old_ctrl = ctrl[slot];
455            self.table.growth_left -= usize::from(old_ctrl.special_is_empty());
456            ctrl[slot] = Tag::full(hash);
457            self.table.items += 1;
458        }
459
460        let bucket = ctx.bucket_ptr(slot, size_of::<T>()).cast::<T>();
461
462        *bucket = value;
463    }
464}
465
466#[derive(Debug)]
467pub struct RawTableInner {
468    ctrl: InvPtr<u8>,
469    bucket_mask: usize,
470    growth_left: usize,
471    items: usize,
472}
473
474impl RawTableInner {
475    pub fn new() -> Self {
476        Self {
477            ctrl: InvPtr::null(),
478            bucket_mask: 0,
479            growth_left: 0,
480            items: 0,
481        }
482    }
483
484    pub fn len(&self) -> usize {
485        self.items
486    }
487
488    pub fn buckets(&self) -> usize {
489        self.bucket_mask + 1
490    }
491
492    pub fn is_empty(&self) -> bool {
493        self.len() == 0
494    }
495
496    fn is_empty_singleton(&self) -> bool {
497        self.bucket_mask == 0
498    }
499}
500
501impl RawTableInner {
502    fn probe_seq(&self, hash: u64) -> ProbeSeq {
503        ProbeSeq {
504            pos: h1(hash) & self.bucket_mask,
505        }
506    }
507
508    unsafe fn find_or_find_insert_slot_inner(
509        &self,
510        hash: u64,
511        eq: &mut dyn FnMut(usize) -> bool,
512        ctx: &impl Ctx,
513    ) -> std::result::Result<usize, usize> {
514        let tag_hash = Tag::full(hash);
515        let mut probe_seq = self.probe_seq(hash);
516
517        let ctrl = ctx.ctrl(self.buckets());
518        loop {
519            let tag = ctrl[probe_seq.pos];
520
521            if tag.eq(&tag_hash) && eq(probe_seq.pos) {
522                return Ok(probe_seq.pos);
523            }
524
525            // If the tag is empty that means that the index couldn't be in subsequent elements
526            // since if the tag was deleted there's a still a change the element could be
527            // further down the line
528            if tag.is_special() && tag.special_is_empty() {
529                return Err(probe_seq.pos);
530            }
531
532            probe_seq.move_next(self.bucket_mask);
533        }
534    }
535
536    unsafe fn find_insert_slot(&self, hash: u64, ctx: &impl Ctx) -> usize {
537        let mut probe_seq = self.probe_seq(hash);
538        let ctrl_slice = ctx.ctrl(self.buckets());
539        loop {
540            let tag = ctrl_slice[probe_seq.pos];
541            if tag.is_special() {
542                return probe_seq.pos;
543            }
544            probe_seq.move_next(self.bucket_mask);
545        }
546    }
547
548    unsafe fn find_inner(
549        &self,
550        hash: u64,
551        eq: &mut dyn FnMut(usize) -> bool,
552        ctx: &impl Ctx,
553    ) -> Option<usize> {
554        debug_assert!(!self.is_empty());
555
556        let tag_hash = Tag::full(hash);
557        let mut probe_seq = self.probe_seq(hash);
558        let ctrl_slice = ctx.ctrl(self.buckets());
559
560        loop {
561            let tag = ctrl_slice[probe_seq.pos];
562
563            if tag.eq(&tag_hash) && eq(probe_seq.pos) {
564                return Some(probe_seq.pos);
565            }
566
567            if tag.is_special() && tag.special_is_empty() {
568                return None;
569            }
570
571            probe_seq.move_next(self.bucket_mask);
572        }
573    }
574
575    unsafe fn reserve_rehash_inner(
576        &mut self,
577        alloc: &HashTableAlloc,
578        additional: usize,
579        hasher: &dyn Fn(&mut Self, usize) -> u64,
580        table_layout: &TableLayout,
581        drop: Option<unsafe fn(*mut u8)>,
582        ctx: &impl CtxMut,
583    ) -> Result<()> {
584        let new_items = self.items + additional;
585        let full_capacity = bucket_mask_to_capacity(self.bucket_mask);
586        if new_items <= full_capacity / 2 {
587            self.rehash_in_place(hasher, table_layout.size, drop, ctx);
588        } else {
589            self.resize_inner(
590                alloc,
591                usize::max(new_items, full_capacity + 1),
592                hasher,
593                table_layout,
594                ctx,
595            )?
596        }
597
598        Ok(())
599    }
600
601    // to avoid unnecessary work between rehash_in_place and resize_inner
602    unsafe fn rehash_in_place(
603        &mut self,
604        hasher: &dyn Fn(&mut Self, usize) -> u64,
605        size_of: usize,
606        drop: Option<unsafe fn(*mut u8)>,
607        ctx: &impl CtxMut,
608    ) {
609        self.prepare_rehash_in_place(ctx);
610
611        self.rehash_in_place_inner(hasher, size_of, self.buckets(), drop, ctx);
612    }
613
614    unsafe fn rehash_in_place_inner(
615        &mut self,
616        hasher: &dyn Fn(&mut Self, usize) -> u64,
617        size_of: usize,
618        workable_buckets: usize,
619        _drop: Option<unsafe fn(*mut u8)>,
620        ctx: &impl CtxMut,
621    ) {
622        // since this is also called by resize inner, in that case we don't need to work on all the
623        // buckets just the area that may contain deleted entries.
624        let ctrl = ctx.ctrl_mut(self.buckets());
625
626        'outer: for i in 0..workable_buckets {
627            if ctrl[i] != Tag::DELETED {
628                continue;
629            }
630
631            let i_p = ctx.bucket_ptr(i, size_of);
632            'inner: loop {
633                let hash = hasher(self, i);
634
635                let new_i = self.find_insert_slot(hash, ctx);
636
637                if i == new_i {
638                    self.replace_ctrl_hash(new_i, hash, ctx);
639                    continue 'outer;
640                }
641
642                let new_i_p = ctx.bucket_ptr(new_i, size_of);
643
644                let prev_ctrl = self.replace_ctrl_hash(new_i, hash, ctx);
645                if prev_ctrl == Tag::EMPTY {
646                    ctrl[i] = Tag::EMPTY;
647                    std::ptr::copy_nonoverlapping(i_p, new_i_p, size_of);
648                    continue 'outer;
649                } else {
650                    debug_assert_eq!(prev_ctrl, Tag::DELETED);
651                    std::ptr::swap_nonoverlapping(i_p, new_i_p, size_of);
652                    continue 'inner;
653                }
654            }
655        }
656
657        self.growth_left = bucket_mask_to_capacity(self.bucket_mask) - self.items;
658    }
659
660    unsafe fn prepare_rehash_in_place(&mut self, ctx: &impl CtxMut) {
661        if self.is_empty() {
662            return;
663        }
664
665        let slice = ctx.ctrl_mut(self.buckets());
666        for i in 0..self.buckets() {
667            if slice[i].is_full() {
668                slice[i] = Tag::DELETED;
669            } else {
670                slice[i] = Tag::EMPTY;
671            }
672        }
673    }
674
675    unsafe fn bootstrap(
676        &mut self,
677        alloc: &HashTableAlloc,
678        capacity: usize,
679        table_layout: &TableLayout,
680    ) -> Result<()> {
681        let buckets = std::cmp::max((capacity * 8 / 7).next_power_of_two(), 8);
682
683        let global = alloc.allocate(table_layout, buckets)?;
684        self.ctrl = InvPtr::new(Ref::from_ptr(self), global)?;
685        self.bucket_mask = buckets - 1;
686        self.growth_left = bucket_mask_to_capacity(self.bucket_mask);
687        self.items = 0;
688
689        self.ctrl_slice_mut().fill_empty();
690
691        Ok(())
692    }
693
694    // I'm using alloc instead of realloc here, making an assumption about how the allocator works
695    // but this gets me half the potential capacity of an object I need to do a rehash in place
696    // instead but with two different sizes on the same array?
697    // But once I create a sharding wrapper the amount a single object holds matters less.
698    // TODO: Redo this once a real allocation function is made
699    unsafe fn resize_inner(
700        &mut self,
701        alloc: &HashTableAlloc,
702        capacity: usize,
703        hasher: &dyn Fn(&mut Self, usize) -> u64,
704        table_layout: &TableLayout,
705        ctx: &impl CtxMut,
706    ) -> Result<()> {
707        debug_assert!(self.items <= capacity);
708
709        let old_buckets = self.buckets();
710
711        // To not waste work, we are preparing resize before we empty the tags to not operate on
712        // tags we know are empty Allocate the tags in place, and empty them out
713        let buckets = std::cmp::max((capacity * 8 / 7).next_power_of_two(), 8);
714        let global = alloc.allocate(table_layout, buckets)?;
715        if !self.is_empty_singleton() {
716            self.prepare_rehash_in_place(ctx);
717            let p = ctx.base_mut().add(self.buckets()).cast::<Tag>();
718            let slice = std::slice::from_raw_parts_mut(p, buckets - self.buckets());
719            slice.fill_empty();
720
721            self.bucket_mask = buckets - 1;
722            self.growth_left = bucket_mask_to_capacity(self.bucket_mask) - self.items;
723            self.rehash_in_place_inner(hasher, table_layout.size, old_buckets, None, ctx);
724        } else {
725            self.ctrl = InvPtr::new(Ref::from_ptr(self), global)?;
726            self.bucket_mask = buckets - 1;
727            self.growth_left = bucket_mask_to_capacity(self.bucket_mask);
728            self.items = 0;
729
730            self.ctrl_slice_mut().fill_empty();
731        }
732
733        Ok(())
734    }
735
736    unsafe fn erase(&mut self, index: usize) {
737        self.set_ctrl(index, Tag::DELETED);
738        self.items -= 1;
739    }
740
741    unsafe fn bucket<T: Invariant>(&self, index: usize) -> Ref<T> {
742        unsafe { self.ctrl.resolve().cast::<T>().sub(index + 1) }
743    }
744
745    #[inline]
746    unsafe fn replace_ctrl_hash(&mut self, index: usize, hash: u64, ctx: &impl CtxMut) -> Tag {
747        let ctrl = ctx.ctrl_mut(self.buckets());
748        let prev_ctrl = ctrl[index];
749        ctrl[index] = Tag::full(hash);
750        prev_ctrl.to_owned()
751    }
752
753    #[inline]
754    unsafe fn set_ctrl(&mut self, index: usize, ctrl: Tag) {
755        self.ctrl_slice_mut()[index] = ctrl;
756    }
757
758    #[inline]
759    unsafe fn ctrl_slice_mut(&mut self) -> RefSliceMut<'_, Tag> {
760        let r = self.ctrl.resolve().cast::<Tag>().into_mut();
761        let slice = RefSliceMut::from_ref(r, self.buckets());
762        slice
763    }
764
765    // This function isn't responsible for guaranteeing mutability
766    unsafe fn iter<T: Invariant>(&self) -> RawIter<T> {
767        let data = self.ctrl.resolve().raw().cast::<T>().sub(1).cast_mut();
768        RawIter::new(
769            self.ctrl.resolve().raw().cast(),
770            NonNull::new(data).unwrap(),
771            self.buckets(),
772        )
773    }
774}
775
776// Assumes there exists a valid hashtable and object handle to it.
777// also that the underlying hashtable doesn't change size
778pub struct RawIter<T: Invariant> {
779    data: NonNull<T>,
780    ctrl: *const Tag,
781    top: *const Tag,
782}
783
784impl<T: Invariant> RawIter<T> {
785    pub(crate) unsafe fn new(ctrl: *const Tag, data: NonNull<T>, len: usize) -> RawIter<T> {
786        let end = ctrl.add(len);
787
788        Self {
789            data,
790            ctrl,
791            top: end,
792        }
793    }
794
795    pub(crate) unsafe fn next_impl(&mut self) -> Option<NonNull<T>> {
796        loop {
797            if self.ctrl >= self.top {
798                return None;
799            }
800
801            if (*self.ctrl).is_full() {
802                self.data = self.data.sub(1);
803                self.ctrl = self.ctrl.add(1);
804                return Some(self.data.add(1));
805            }
806            self.data = self.data.sub(1);
807            self.ctrl = self.ctrl.add(1);
808        }
809    }
810}
811
812impl<T: Invariant> Iterator for RawIter<T> {
813    type Item = NonNull<T>;
814
815    fn next(&mut self) -> Option<Self::Item> {
816        unsafe { self.next_impl() }
817    }
818}