@@ -3,14 +3,14 @@ use crate::scopeguard::guard;
3
3
use crate :: TryReserveError ;
4
4
#[ cfg( feature = "nightly" ) ]
5
5
use crate :: UnavailableMutError ;
6
- use core:: hint;
7
6
use core:: iter:: FusedIterator ;
8
7
use core:: marker:: PhantomData ;
9
8
use core:: mem;
10
9
use core:: mem:: ManuallyDrop ;
11
10
#[ cfg( feature = "nightly" ) ]
12
11
use core:: mem:: MaybeUninit ;
13
12
use core:: ptr:: NonNull ;
13
+ use core:: { hint, ptr} ;
14
14
15
15
cfg_if ! {
16
16
// Use the SSE2 implementation if possible: it allows us to scan 16 buckets
@@ -359,6 +359,7 @@ impl<T> Bucket<T> {
359
359
pub unsafe fn as_mut < ' a > ( & self ) -> & ' a mut T {
360
360
& mut * self . as_ptr ( )
361
361
}
362
+ #[ cfg( feature = "raw" ) ]
362
363
#[ cfg_attr( feature = "inline-more" , inline) ]
363
364
pub unsafe fn copy_from_nonoverlapping ( & self , other : & Self ) {
364
365
self . as_ptr ( ) . copy_from_nonoverlapping ( other. as_ptr ( ) , 1 ) ;
@@ -682,24 +683,14 @@ impl<T, A: Allocator + Clone> RawTable<T, A> {
682
683
hasher : impl Fn ( & T ) -> u64 ,
683
684
fallibility : Fallibility ,
684
685
) -> 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 ( ) ) ,
702
690
fallibility,
691
+ TableLayout :: new :: < T > ( ) ,
692
+ mem:: transmute ( ptr:: drop_in_place :: < T > as unsafe fn ( * mut T ) ) ,
693
+ mem:: needs_drop :: < T > ( ) ,
703
694
)
704
695
}
705
696
}
@@ -708,76 +699,15 @@ impl<T, A: Allocator + Clone> RawTable<T, A> {
708
699
/// allocation).
709
700
///
710
701
/// If `hasher` panics then some the table's contents may be lost.
702
+ #[ cfg( test) ]
711
703
fn rehash_in_place ( & mut self , hasher : impl Fn ( & T ) -> u64 ) {
712
704
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
+ ) ;
781
711
}
782
712
}
783
713
@@ -790,30 +720,12 @@ impl<T, A: Allocator + Clone> RawTable<T, A> {
790
720
fallibility : Fallibility ,
791
721
) -> Result < ( ) , TryReserveError > {
792
722
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
+ )
817
729
}
818
730
}
819
731
@@ -1312,6 +1224,19 @@ impl<A: Allocator + Clone> RawTableInner<A> {
1312
1224
Bucket :: from_base_index ( self . data_end ( ) , index)
1313
1225
}
1314
1226
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
+
1315
1240
#[ cfg_attr( feature = "inline-more" , inline) ]
1316
1241
unsafe fn data_end < T > ( & self ) -> NonNull < T > {
1317
1242
NonNull :: new_unchecked ( self . ctrl . as_ptr ( ) . cast ( ) )
@@ -1457,6 +1382,180 @@ impl<A: Allocator + Clone> RawTableInner<A> {
1457
1382
} ) )
1458
1383
}
1459
1384
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
+
1460
1559
#[ inline]
1461
1560
unsafe fn free_buckets ( & mut self , table_layout : TableLayout ) {
1462
1561
// Avoid `Option::unwrap_or_else` because it bloats LLVM IR.
0 commit comments