@@ -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,102 +683,18 @@ 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,
702
- fallibility,
703
- )
704
- }
705
- }
706
-
707
- /// Rehashes the contents of the table in place (i.e. without changing the
708
- /// allocation).
709
- ///
710
- /// If `hasher` panics then some the table's contents may be lost.
711
- fn rehash_in_place ( & mut self , hasher : impl Fn ( & T ) -> u64 ) {
712
686
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_| {
687
+ self . table . reserve_rehash_inner (
688
+ additional,
689
+ & |table, index| hasher ( table. bucket :: < T > ( index) . as_ref ( ) ) ,
690
+ fallibility,
691
+ TableLayout :: new :: < T > ( ) ,
720
692
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) ;
693
+ Some ( mem:: transmute ( ptr:: drop_in_place :: < T > as unsafe fn ( * mut T ) ) )
694
+ } else {
695
+ None
696
+ } ,
697
+ )
781
698
}
782
699
}
783
700
@@ -790,30 +707,12 @@ impl<T, A: Allocator + Clone> RawTable<T, A> {
790
707
fallibility : Fallibility ,
791
708
) -> Result < ( ) , TryReserveError > {
792
709
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 ( ( ) )
710
+ self . table . resize_inner (
711
+ capacity,
712
+ & |table, index| hasher ( table. bucket :: < T > ( index) . as_ref ( ) ) ,
713
+ fallibility,
714
+ TableLayout :: new :: < T > ( ) ,
715
+ )
817
716
}
818
717
}
819
718
@@ -1312,6 +1211,14 @@ impl<A: Allocator + Clone> RawTableInner<A> {
1312
1211
Bucket :: from_base_index ( self . data_end ( ) , index)
1313
1212
}
1314
1213
1214
+ #[ cfg_attr( feature = "inline-more" , inline) ]
1215
+ unsafe fn bucket_ptr ( & self , index : usize , size_of : usize ) -> * mut u8 {
1216
+ debug_assert_ne ! ( self . bucket_mask, 0 ) ;
1217
+ debug_assert ! ( index < self . buckets( ) ) ;
1218
+ let base: * mut u8 = self . data_end ( ) . as_ptr ( ) ;
1219
+ base. sub ( ( index + 1 ) * size_of)
1220
+ }
1221
+
1315
1222
#[ cfg_attr( feature = "inline-more" , inline) ]
1316
1223
unsafe fn data_end < T > ( & self ) -> NonNull < T > {
1317
1224
NonNull :: new_unchecked ( self . ctrl . as_ptr ( ) . cast ( ) )
@@ -1457,6 +1364,178 @@ impl<A: Allocator + Clone> RawTableInner<A> {
1457
1364
} ) )
1458
1365
}
1459
1366
1367
+ /// Reserves or rehashes to make room for `additional` more elements.
1368
+ ///
1369
+ /// This uses dynamic dispatch to reduce the amount of
1370
+ /// code generated, but it is eliminated by LLVM optimizations when inlined.
1371
+ #[ allow( clippy:: inline_always) ]
1372
+ #[ inline( always) ]
1373
+ unsafe fn reserve_rehash_inner (
1374
+ & mut self ,
1375
+ additional : usize ,
1376
+ hasher : & dyn Fn ( & mut Self , usize ) -> u64 ,
1377
+ fallibility : Fallibility ,
1378
+ layout : TableLayout ,
1379
+ drop : Option < fn ( * mut u8 ) > ,
1380
+ ) -> Result < ( ) , TryReserveError > {
1381
+ // Avoid `Option::ok_or_else` because it bloats LLVM IR.
1382
+ let new_items = match self . items . checked_add ( additional) {
1383
+ Some ( new_items) => new_items,
1384
+ None => return Err ( fallibility. capacity_overflow ( ) ) ,
1385
+ } ;
1386
+ let full_capacity = bucket_mask_to_capacity ( self . bucket_mask ) ;
1387
+ if new_items <= full_capacity / 2 {
1388
+ // Rehash in-place without re-allocating if we have plenty of spare
1389
+ // capacity that is locked up due to DELETED entries.
1390
+ self . rehash_in_place ( hasher, layout. size , drop) ;
1391
+ Ok ( ( ) )
1392
+ } else {
1393
+ // Otherwise, conservatively resize to at least the next size up
1394
+ // to avoid churning deletes into frequent rehashes.
1395
+ self . resize_inner (
1396
+ usize:: max ( new_items, full_capacity + 1 ) ,
1397
+ hasher,
1398
+ fallibility,
1399
+ layout,
1400
+ )
1401
+ }
1402
+ }
1403
+
1404
+ /// Allocates a new table of a different size and moves the contents of the
1405
+ /// current table into it.
1406
+ ///
1407
+ /// This uses dynamic dispatch to reduce the amount of
1408
+ /// code generated, but it is eliminated by LLVM optimizations when inlined.
1409
+ #[ allow( clippy:: inline_always) ]
1410
+ #[ inline( always) ]
1411
+ unsafe fn resize_inner (
1412
+ & mut self ,
1413
+ capacity : usize ,
1414
+ hasher : & dyn Fn ( & mut Self , usize ) -> u64 ,
1415
+ fallibility : Fallibility ,
1416
+ layout : TableLayout ,
1417
+ ) -> Result < ( ) , TryReserveError > {
1418
+ let mut new_table = self . prepare_resize ( layout, capacity, fallibility) ?;
1419
+
1420
+ // Copy all elements to the new table.
1421
+ for i in 0 ..self . buckets ( ) {
1422
+ if !is_full ( * self . ctrl ( i) ) {
1423
+ continue ;
1424
+ }
1425
+
1426
+ // This may panic.
1427
+ let hash = hasher ( self , i) ;
1428
+
1429
+ // We can use a simpler version of insert() here since:
1430
+ // - there are no DELETED entries.
1431
+ // - we know there is enough space in the table.
1432
+ // - all elements are unique.
1433
+ let ( index, _) = new_table. prepare_insert_slot ( hash) ;
1434
+
1435
+ ptr:: copy_nonoverlapping (
1436
+ self . bucket_ptr ( i, layout. size ) ,
1437
+ new_table. bucket_ptr ( index, layout. size ) ,
1438
+ layout. size ,
1439
+ ) ;
1440
+ }
1441
+
1442
+ // We successfully copied all elements without panicking. Now replace
1443
+ // self with the new table. The old table will have its memory freed but
1444
+ // the items will not be dropped (since they have been moved into the
1445
+ // new table).
1446
+ mem:: swap ( self , & mut new_table) ;
1447
+
1448
+ Ok ( ( ) )
1449
+ }
1450
+
1451
+ /// Rehashes the contents of the table in place (i.e. without changing the
1452
+ /// allocation).
1453
+ ///
1454
+ /// If `hasher` panics then some the table's contents may be lost.
1455
+ ///
1456
+ /// This uses dynamic dispatch to reduce the amount of
1457
+ /// code generated, but it is eliminated by LLVM optimizations when inlined.
1458
+ #[ allow( clippy:: inline_always) ]
1459
+ #[ inline( always) ]
1460
+ unsafe fn rehash_in_place (
1461
+ & mut self ,
1462
+ hasher : & dyn Fn ( & mut Self , usize ) -> u64 ,
1463
+ size_of : usize ,
1464
+ drop : Option < fn ( * mut u8 ) > ,
1465
+ ) {
1466
+ // If the hash function panics then properly clean up any elements
1467
+ // that we haven't rehashed yet. We unfortunately can't preserve the
1468
+ // element since we lost their hash and have no way of recovering it
1469
+ // without risking another panic.
1470
+ self . prepare_rehash_in_place ( ) ;
1471
+
1472
+ let mut guard = guard ( self , move |self_| {
1473
+ if let Some ( drop) = drop {
1474
+ for i in 0 ..self_. buckets ( ) {
1475
+ if * self_. ctrl ( i) == DELETED {
1476
+ self_. set_ctrl ( i, EMPTY ) ;
1477
+ drop ( self_. bucket_ptr ( i, size_of) ) ;
1478
+ self_. items -= 1 ;
1479
+ }
1480
+ }
1481
+ }
1482
+ self_. growth_left = bucket_mask_to_capacity ( self_. bucket_mask ) - self_. items ;
1483
+ } ) ;
1484
+
1485
+ // At this point, DELETED elements are elements that we haven't
1486
+ // rehashed yet. Find them and re-insert them at their ideal
1487
+ // position.
1488
+ ' outer: for i in 0 ..guard. buckets ( ) {
1489
+ if * guard. ctrl ( i) != DELETED {
1490
+ continue ;
1491
+ }
1492
+
1493
+ let i_p = guard. bucket_ptr ( i, size_of) ;
1494
+
1495
+ ' inner: loop {
1496
+ // Hash the current item
1497
+ let hash = hasher ( * guard, i) ;
1498
+
1499
+ // Search for a suitable place to put it
1500
+ let new_i = guard. find_insert_slot ( hash) ;
1501
+ let new_i_p = guard. bucket_ptr ( new_i, size_of) ;
1502
+
1503
+ // Probing works by scanning through all of the control
1504
+ // bytes in groups, which may not be aligned to the group
1505
+ // size. If both the new and old position fall within the
1506
+ // same unaligned group, then there is no benefit in moving
1507
+ // it and we can just continue to the next item.
1508
+ if likely ( guard. is_in_same_group ( i, new_i, hash) ) {
1509
+ guard. set_ctrl_h2 ( i, hash) ;
1510
+ continue ' outer;
1511
+ }
1512
+
1513
+ // We are moving the current item to a new position. Write
1514
+ // our H2 to the control byte of the new position.
1515
+ let prev_ctrl = guard. replace_ctrl_h2 ( new_i, hash) ;
1516
+ if prev_ctrl == EMPTY {
1517
+ guard. set_ctrl ( i, EMPTY ) ;
1518
+ // If the target slot is empty, simply move the current
1519
+ // element into the new slot and clear the old control
1520
+ // byte.
1521
+ ptr:: copy_nonoverlapping ( i_p, new_i_p, size_of) ;
1522
+ continue ' outer;
1523
+ } else {
1524
+ // If the target slot is occupied, swap the two elements
1525
+ // and then continue processing the element that we just
1526
+ // swapped into the old slot.
1527
+ debug_assert_eq ! ( prev_ctrl, DELETED ) ;
1528
+ ptr:: swap_nonoverlapping ( i_p, new_i_p, size_of) ;
1529
+ continue ' inner;
1530
+ }
1531
+ }
1532
+ }
1533
+
1534
+ guard. growth_left = bucket_mask_to_capacity ( guard. bucket_mask ) - guard. items ;
1535
+
1536
+ mem:: forget ( guard) ;
1537
+ }
1538
+
1460
1539
#[ inline]
1461
1540
unsafe fn free_buckets ( & mut self , table_layout : TableLayout ) {
1462
1541
// Avoid `Option::unwrap_or_else` because it bloats LLVM IR.
@@ -2281,6 +2360,20 @@ impl<'a, A: Allocator + Clone> Iterator for RawIterHashInner<'a, A> {
2281
2360
mod test_map {
2282
2361
use super :: * ;
2283
2362
2363
+ fn rehash_in_place < T > ( table : & mut RawTable < T > , hasher : impl Fn ( & T ) -> u64 ) {
2364
+ unsafe {
2365
+ table. table . rehash_in_place (
2366
+ & |table, index| hasher ( table. bucket :: < T > ( index) . as_ref ( ) ) ,
2367
+ mem:: size_of :: < T > ( ) ,
2368
+ if mem:: needs_drop :: < T > ( ) {
2369
+ Some ( mem:: transmute ( ptr:: drop_in_place :: < T > as unsafe fn ( * mut T ) ) )
2370
+ } else {
2371
+ None
2372
+ } ,
2373
+ ) ;
2374
+ }
2375
+ }
2376
+
2284
2377
#[ test]
2285
2378
fn rehash ( ) {
2286
2379
let mut table = RawTable :: new ( ) ;
@@ -2296,7 +2389,7 @@ mod test_map {
2296
2389
assert ! ( table. find( i + 100 , |x| * x == i + 100 ) . is_none( ) ) ;
2297
2390
}
2298
2391
2299
- table . rehash_in_place ( hasher) ;
2392
+ rehash_in_place ( & mut table , hasher) ;
2300
2393
2301
2394
for i in 0 ..100 {
2302
2395
unsafe {
0 commit comments