@@ -3,14 +3,14 @@ use crate::scopeguard::guard;
33use crate :: TryReserveError ;
44#[ cfg( feature = "nightly" ) ]
55use crate :: UnavailableMutError ;
6- use core:: hint;
76use core:: iter:: FusedIterator ;
87use core:: marker:: PhantomData ;
98use core:: mem;
109use core:: mem:: ManuallyDrop ;
1110#[ cfg( feature = "nightly" ) ]
1211use core:: mem:: MaybeUninit ;
1312use core:: ptr:: NonNull ;
13+ use core:: { hint, ptr} ;
1414
1515cfg_if ! {
1616 // Use the SSE2 implementation if possible: it allows us to scan 16 buckets
@@ -359,6 +359,7 @@ impl<T> Bucket<T> {
359359 pub unsafe fn as_mut < ' a > ( & self ) -> & ' a mut T {
360360 & mut * self . as_ptr ( )
361361 }
362+ #[ cfg( feature = "raw" ) ]
362363 #[ cfg_attr( feature = "inline-more" , inline) ]
363364 pub unsafe fn copy_from_nonoverlapping ( & self , other : & Self ) {
364365 self . as_ptr ( ) . copy_from_nonoverlapping ( other. as_ptr ( ) , 1 ) ;
@@ -682,24 +683,14 @@ impl<T, A: Allocator + Clone> RawTable<T, A> {
682683 hasher : impl Fn ( & T ) -> u64 ,
683684 fallibility : Fallibility ,
684685 ) -> Result < ( ) , TryReserveError > {
685- // Avoid `Option::ok_or_else` because it bloats LLVM IR.
686- let new_items = match self . table . items . checked_add ( additional) {
687- Some ( new_items) => new_items,
688- None => return Err ( fallibility. capacity_overflow ( ) ) ,
689- } ;
690- let full_capacity = bucket_mask_to_capacity ( self . table . bucket_mask ) ;
691- if new_items <= full_capacity / 2 {
692- // Rehash in-place without re-allocating if we have plenty of spare
693- // capacity that is locked up due to DELETED entries.
694- self . rehash_in_place ( hasher) ;
695- Ok ( ( ) )
696- } else {
697- // Otherwise, conservatively resize to at least the next size up
698- // to avoid churning deletes into frequent rehashes.
699- self . resize (
700- usize:: max ( new_items, full_capacity + 1 ) ,
701- hasher,
686+ unsafe {
687+ self . table . reserve_rehash_inner (
688+ additional,
689+ & |table, index| hasher ( table. bucket :: < T > ( index) . as_ref ( ) ) ,
702690 fallibility,
691+ TableLayout :: new :: < T > ( ) ,
692+ mem:: transmute ( ptr:: drop_in_place :: < T > as unsafe fn ( * mut T ) ) ,
693+ mem:: needs_drop :: < T > ( ) ,
703694 )
704695 }
705696 }
@@ -708,76 +699,15 @@ impl<T, A: Allocator + Clone> RawTable<T, A> {
708699 /// allocation).
709700 ///
710701 /// If `hasher` panics then some the table's contents may be lost.
702+ #[ cfg( test) ]
711703 fn rehash_in_place ( & mut self , hasher : impl Fn ( & T ) -> u64 ) {
712704 unsafe {
713- // If the hash function panics then properly clean up any elements
714- // that we haven't rehashed yet. We unfortunately can't preserve the
715- // element since we lost their hash and have no way of recovering it
716- // without risking another panic.
717- self . table . prepare_rehash_in_place ( ) ;
718-
719- let mut guard = guard ( & mut self . table , move |self_| {
720- if mem:: needs_drop :: < T > ( ) {
721- for i in 0 ..self_. buckets ( ) {
722- if * self_. ctrl ( i) == DELETED {
723- self_. set_ctrl ( i, EMPTY ) ;
724- self_. bucket :: < T > ( i) . drop ( ) ;
725- self_. items -= 1 ;
726- }
727- }
728- }
729- self_. growth_left = bucket_mask_to_capacity ( self_. bucket_mask ) - self_. items ;
730- } ) ;
731-
732- // At this point, DELETED elements are elements that we haven't
733- // rehashed yet. Find them and re-insert them at their ideal
734- // position.
735- ' outer: for i in 0 ..guard. buckets ( ) {
736- if * guard. ctrl ( i) != DELETED {
737- continue ;
738- }
739-
740- ' inner: loop {
741- // Hash the current item
742- let item = guard. bucket ( i) ;
743- let hash = hasher ( item. as_ref ( ) ) ;
744-
745- // Search for a suitable place to put it
746- let new_i = guard. find_insert_slot ( hash) ;
747-
748- // Probing works by scanning through all of the control
749- // bytes in groups, which may not be aligned to the group
750- // size. If both the new and old position fall within the
751- // same unaligned group, then there is no benefit in moving
752- // it and we can just continue to the next item.
753- if likely ( guard. is_in_same_group ( i, new_i, hash) ) {
754- guard. set_ctrl_h2 ( i, hash) ;
755- continue ' outer;
756- }
757-
758- // We are moving the current item to a new position. Write
759- // our H2 to the control byte of the new position.
760- let prev_ctrl = guard. replace_ctrl_h2 ( new_i, hash) ;
761- if prev_ctrl == EMPTY {
762- guard. set_ctrl ( i, EMPTY ) ;
763- // If the target slot is empty, simply move the current
764- // element into the new slot and clear the old control
765- // byte.
766- guard. bucket ( new_i) . copy_from_nonoverlapping ( & item) ;
767- continue ' outer;
768- } else {
769- // If the target slot is occupied, swap the two elements
770- // and then continue processing the element that we just
771- // swapped into the old slot.
772- debug_assert_eq ! ( prev_ctrl, DELETED ) ;
773- mem:: swap ( guard. bucket ( new_i) . as_mut ( ) , item. as_mut ( ) ) ;
774- continue ' inner;
775- }
776- }
777- }
778-
779- guard. growth_left = bucket_mask_to_capacity ( guard. bucket_mask ) - guard. items ;
780- mem:: forget ( guard) ;
705+ self . table . rehash_in_place (
706+ & |table, index| hasher ( table. bucket :: < T > ( index) . as_ref ( ) ) ,
707+ mem:: size_of :: < T > ( ) ,
708+ mem:: transmute ( ptr:: drop_in_place :: < T > as unsafe fn ( * mut T ) ) ,
709+ mem:: needs_drop :: < T > ( ) ,
710+ ) ;
781711 }
782712 }
783713
@@ -790,30 +720,12 @@ impl<T, A: Allocator + Clone> RawTable<T, A> {
790720 fallibility : Fallibility ,
791721 ) -> Result < ( ) , TryReserveError > {
792722 unsafe {
793- let mut new_table =
794- self . table
795- . prepare_resize ( TableLayout :: new :: < T > ( ) , capacity, fallibility) ?;
796-
797- // Copy all elements to the new table.
798- for item in self . iter ( ) {
799- // This may panic.
800- let hash = hasher ( item. as_ref ( ) ) ;
801-
802- // We can use a simpler version of insert() here since:
803- // - there are no DELETED entries.
804- // - we know there is enough space in the table.
805- // - all elements are unique.
806- let ( index, _) = new_table. prepare_insert_slot ( hash) ;
807- new_table. bucket ( index) . copy_from_nonoverlapping ( & item) ;
808- }
809-
810- // We successfully copied all elements without panicking. Now replace
811- // self with the new table. The old table will have its memory freed but
812- // the items will not be dropped (since they have been moved into the
813- // new table).
814- mem:: swap ( & mut self . table , & mut new_table) ;
815-
816- Ok ( ( ) )
723+ self . table . resize_inner (
724+ capacity,
725+ & |table, index| hasher ( table. bucket :: < T > ( index) . as_ref ( ) ) ,
726+ fallibility,
727+ TableLayout :: new :: < T > ( ) ,
728+ )
817729 }
818730 }
819731
@@ -1312,6 +1224,19 @@ impl<A: Allocator + Clone> RawTableInner<A> {
13121224 Bucket :: from_base_index ( self . data_end ( ) , index)
13131225 }
13141226
1227+ #[ cfg_attr( feature = "inline-more" , inline) ]
1228+ unsafe fn bucket_ptr ( & self , index : usize , size_of : usize ) -> * mut u8 {
1229+ debug_assert_ne ! ( self . bucket_mask, 0 ) ;
1230+ debug_assert ! ( index < self . buckets( ) ) ;
1231+ let base: * mut u8 = self . data_end ( ) . as_ptr ( ) ;
1232+ if size_of == 0 {
1233+ // FIXME: Check if this `data_end` is aligned with ZST?
1234+ base
1235+ } else {
1236+ base. sub ( ( index + 1 ) * size_of)
1237+ }
1238+ }
1239+
13151240 #[ cfg_attr( feature = "inline-more" , inline) ]
13161241 unsafe fn data_end < T > ( & self ) -> NonNull < T > {
13171242 NonNull :: new_unchecked ( self . ctrl . as_ptr ( ) . cast ( ) )
@@ -1457,6 +1382,180 @@ impl<A: Allocator + Clone> RawTableInner<A> {
14571382 } ) )
14581383 }
14591384
1385+ /// Reserves or rehashes to make room for `additional` more elements.
1386+ ///
1387+ /// This uses dynamic dispatch to reduce the amount of
1388+ /// code generated, but it is eliminated by LLVM optimizations when inlined.
1389+ #[ allow( clippy:: inline_always) ]
1390+ #[ inline( always) ]
1391+ unsafe fn reserve_rehash_inner (
1392+ & mut self ,
1393+ additional : usize ,
1394+ hasher : & dyn Fn ( & mut Self , usize ) -> u64 ,
1395+ fallibility : Fallibility ,
1396+ layout : TableLayout ,
1397+ drop : fn ( * mut u8 ) ,
1398+ drops : bool ,
1399+ ) -> Result < ( ) , TryReserveError > {
1400+ // Avoid `Option::ok_or_else` because it bloats LLVM IR.
1401+ let new_items = match self . items . checked_add ( additional) {
1402+ Some ( new_items) => new_items,
1403+ None => return Err ( fallibility. capacity_overflow ( ) ) ,
1404+ } ;
1405+ let full_capacity = bucket_mask_to_capacity ( self . bucket_mask ) ;
1406+ if new_items <= full_capacity / 2 {
1407+ // Rehash in-place without re-allocating if we have plenty of spare
1408+ // capacity that is locked up due to DELETED entries.
1409+ self . rehash_in_place ( hasher, layout. size , drop, drops) ;
1410+ Ok ( ( ) )
1411+ } else {
1412+ // Otherwise, conservatively resize to at least the next size up
1413+ // to avoid churning deletes into frequent rehashes.
1414+ self . resize_inner (
1415+ usize:: max ( new_items, full_capacity + 1 ) ,
1416+ hasher,
1417+ fallibility,
1418+ layout,
1419+ )
1420+ }
1421+ }
1422+
1423+ /// Allocates a new table of a different size and moves the contents of the
1424+ /// current table into it.
1425+ ///
1426+ /// This uses dynamic dispatch to reduce the amount of
1427+ /// code generated, but it is eliminated by LLVM optimizations when inlined.
1428+ #[ allow( clippy:: inline_always) ]
1429+ #[ inline( always) ]
1430+ unsafe fn resize_inner (
1431+ & mut self ,
1432+ capacity : usize ,
1433+ hasher : & dyn Fn ( & mut Self , usize ) -> u64 ,
1434+ fallibility : Fallibility ,
1435+ layout : TableLayout ,
1436+ ) -> Result < ( ) , TryReserveError > {
1437+ let mut new_table = self . prepare_resize ( layout, capacity, fallibility) ?;
1438+
1439+ // Copy all elements to the new table.
1440+ for i in 0 ..self . buckets ( ) {
1441+ if !is_full ( * self . ctrl ( i) ) {
1442+ continue ;
1443+ }
1444+
1445+ // This may panic.
1446+ let hash = hasher ( self , i) ;
1447+
1448+ // We can use a simpler version of insert() here since:
1449+ // - there are no DELETED entries.
1450+ // - we know there is enough space in the table.
1451+ // - all elements are unique.
1452+ let ( index, _) = new_table. prepare_insert_slot ( hash) ;
1453+
1454+ ptr:: copy_nonoverlapping (
1455+ self . bucket_ptr ( i, layout. size ) ,
1456+ new_table. bucket_ptr ( index, layout. size ) ,
1457+ layout. size ,
1458+ ) ;
1459+ }
1460+
1461+ // We successfully copied all elements without panicking. Now replace
1462+ // self with the new table. The old table will have its memory freed but
1463+ // the items will not be dropped (since they have been moved into the
1464+ // new table).
1465+ mem:: swap ( self , & mut new_table) ;
1466+
1467+ Ok ( ( ) )
1468+ }
1469+
1470+ /// Rehashes the contents of the table in place (i.e. without changing the
1471+ /// allocation).
1472+ ///
1473+ /// If `hasher` panics then some the table's contents may be lost.
1474+ ///
1475+ /// This uses dynamic dispatch to reduce the amount of
1476+ /// code generated, but it is eliminated by LLVM optimizations when inlined.
1477+ #[ allow( clippy:: inline_always) ]
1478+ #[ inline( always) ]
1479+ unsafe fn rehash_in_place (
1480+ & mut self ,
1481+ hasher : & dyn Fn ( & mut Self , usize ) -> u64 ,
1482+ size_of : usize ,
1483+ drop : fn ( * mut u8 ) ,
1484+ drops : bool ,
1485+ ) {
1486+ // If the hash function panics then properly clean up any elements
1487+ // that we haven't rehashed yet. We unfortunately can't preserve the
1488+ // element since we lost their hash and have no way of recovering it
1489+ // without risking another panic.
1490+ self . prepare_rehash_in_place ( ) ;
1491+
1492+ let mut guard = guard ( self , move |self_| {
1493+ if drops {
1494+ for i in 0 ..self_. buckets ( ) {
1495+ if * self_. ctrl ( i) == DELETED {
1496+ self_. set_ctrl ( i, EMPTY ) ;
1497+ drop ( self_. bucket_ptr ( i, size_of) ) ;
1498+ self_. items -= 1 ;
1499+ }
1500+ }
1501+ }
1502+ self_. growth_left = bucket_mask_to_capacity ( self_. bucket_mask ) - self_. items ;
1503+ } ) ;
1504+
1505+ // At this point, DELETED elements are elements that we haven't
1506+ // rehashed yet. Find them and re-insert them at their ideal
1507+ // position.
1508+ ' outer: for i in 0 ..guard. buckets ( ) {
1509+ if * guard. ctrl ( i) != DELETED {
1510+ continue ;
1511+ }
1512+
1513+ let i_p = guard. bucket_ptr ( i, size_of) ;
1514+
1515+ ' inner: loop {
1516+ // Hash the current item
1517+ let hash = hasher ( * guard, i) ;
1518+
1519+ // Search for a suitable place to put it
1520+ let new_i = guard. find_insert_slot ( hash) ;
1521+ let new_i_p = guard. bucket_ptr ( new_i, size_of) ;
1522+
1523+ // Probing works by scanning through all of the control
1524+ // bytes in groups, which may not be aligned to the group
1525+ // size. If both the new and old position fall within the
1526+ // same unaligned group, then there is no benefit in moving
1527+ // it and we can just continue to the next item.
1528+ if likely ( guard. is_in_same_group ( i, new_i, hash) ) {
1529+ guard. set_ctrl_h2 ( i, hash) ;
1530+ continue ' outer;
1531+ }
1532+
1533+ // We are moving the current item to a new position. Write
1534+ // our H2 to the control byte of the new position.
1535+ let prev_ctrl = guard. replace_ctrl_h2 ( new_i, hash) ;
1536+ if prev_ctrl == EMPTY {
1537+ guard. set_ctrl ( i, EMPTY ) ;
1538+ // If the target slot is empty, simply move the current
1539+ // element into the new slot and clear the old control
1540+ // byte.
1541+ ptr:: copy_nonoverlapping ( i_p, new_i_p, size_of) ;
1542+ continue ' outer;
1543+ } else {
1544+ // If the target slot is occupied, swap the two elements
1545+ // and then continue processing the element that we just
1546+ // swapped into the old slot.
1547+ debug_assert_eq ! ( prev_ctrl, DELETED ) ;
1548+ ptr:: swap_nonoverlapping ( i_p, new_i_p, size_of) ;
1549+ continue ' inner;
1550+ }
1551+ }
1552+ }
1553+
1554+ guard. growth_left = bucket_mask_to_capacity ( guard. bucket_mask ) - guard. items ;
1555+
1556+ mem:: forget ( guard) ;
1557+ }
1558+
14601559 #[ inline]
14611560 unsafe fn free_buckets ( & mut self , table_layout : TableLayout ) {
14621561 // Avoid `Option::unwrap_or_else` because it bloats LLVM IR.
0 commit comments