@@ -6631,3 +6631,136 @@ mod test_map {
6631
6631
) ;
6632
6632
}
6633
6633
}
6634
+
6635
+ #[ cfg( all( test, unix, any( feature = "nightly" , feature = "allocator-api2" ) ) ) ]
6636
+ mod test_map_with_mmap_allocations {
6637
+ use super :: HashMap ;
6638
+ use crate :: raw:: prev_pow2;
6639
+ use core:: alloc:: Layout ;
6640
+ use core:: ptr:: { null_mut, NonNull } ;
6641
+
6642
+ #[ cfg( feature = "nightly" ) ]
6643
+ use core:: alloc:: { AllocError , Allocator } ;
6644
+
6645
+ #[ cfg( all( feature = "allocator-api2" , not( feature = "nightly" ) ) ) ]
6646
+ use allocator_api2:: alloc:: { AllocError , Allocator } ;
6647
+
6648
+ /// This is not a production quality allocator, just good enough for
6649
+ /// some basic tests.
6650
+ #[ derive( Clone , Copy , Debug ) ]
6651
+ struct MmapAllocator {
6652
+ /// Guarantee this is a power of 2.
6653
+ page_size : usize ,
6654
+ }
6655
+
6656
+ impl MmapAllocator {
6657
+ fn new ( ) -> Result < Self , AllocError > {
6658
+ let result = unsafe { libc:: sysconf ( libc:: _SC_PAGESIZE) } ;
6659
+ if result < 1 {
6660
+ return Err ( AllocError ) ;
6661
+ }
6662
+
6663
+ let page_size = result as usize ;
6664
+ if !page_size. is_power_of_two ( ) {
6665
+ Err ( AllocError )
6666
+ } else {
6667
+ Ok ( Self { page_size } )
6668
+ }
6669
+ }
6670
+
6671
+ fn fit_to_page_size ( & self , n : usize ) -> Result < usize , AllocError > {
6672
+ // If n=0, give a single page (wasteful, I know).
6673
+ let n = if n == 0 { self . page_size } else { n } ;
6674
+
6675
+ match n & ( self . page_size - 1 ) {
6676
+ 0 => Ok ( n) ,
6677
+ rem => n. checked_add ( self . page_size - rem) . ok_or ( AllocError ) ,
6678
+ }
6679
+ }
6680
+ }
6681
+
6682
+ unsafe impl Allocator for MmapAllocator {
6683
+ fn allocate ( & self , layout : Layout ) -> Result < NonNull < [ u8 ] > , AllocError > {
6684
+ if layout. align ( ) > self . page_size {
6685
+ return Err ( AllocError ) ;
6686
+ }
6687
+
6688
+ let null = null_mut ( ) ;
6689
+ let len = self . fit_to_page_size ( layout. size ( ) ) ? as libc:: size_t ;
6690
+ let prot = libc:: PROT_READ | libc:: PROT_WRITE ;
6691
+ let flags = libc:: MAP_PRIVATE | libc:: MAP_ANON ;
6692
+ let addr = unsafe { libc:: mmap ( null, len, prot, flags, -1 , 0 ) } ;
6693
+
6694
+ // mmap returns MAP_FAILED on failure, not Null.
6695
+ if addr == libc:: MAP_FAILED {
6696
+ return Err ( AllocError ) ;
6697
+ }
6698
+
6699
+ match NonNull :: new ( addr. cast ( ) ) {
6700
+ Some ( data) => {
6701
+ // SAFETY: this is NonNull::slice_from_raw_parts.
6702
+ Ok ( unsafe {
6703
+ NonNull :: new_unchecked ( core:: ptr:: slice_from_raw_parts_mut (
6704
+ data. as_ptr ( ) ,
6705
+ len,
6706
+ ) )
6707
+ } )
6708
+ }
6709
+
6710
+ // This branch shouldn't be taken in practice, but since we
6711
+ // cannot return null as a valid pointer in our type system,
6712
+ // we attempt to handle it.
6713
+ None => {
6714
+ _ = unsafe { libc:: munmap ( addr, len) } ;
6715
+ Err ( AllocError )
6716
+ }
6717
+ }
6718
+ }
6719
+
6720
+ unsafe fn deallocate ( & self , ptr : NonNull < u8 > , layout : Layout ) {
6721
+ // If they allocated it with this layout, it must round correctly.
6722
+ let size = self . fit_to_page_size ( layout. size ( ) ) . unwrap ( ) ;
6723
+ let _result = libc:: munmap ( ptr. as_ptr ( ) . cast ( ) , size) ;
6724
+ debug_assert_eq ! ( 0 , _result)
6725
+ }
6726
+ }
6727
+
6728
+ #[ test]
6729
+ fn test_tiny_allocation_gets_rounded_to_page_size ( ) {
6730
+ let alloc = MmapAllocator :: new ( ) . unwrap ( ) ;
6731
+ let mut map: HashMap < usize , ( ) , _ , _ > = HashMap :: with_capacity_in ( 1 , alloc) ;
6732
+
6733
+ // Size of an element plus its control byte.
6734
+ let rough_bucket_size = core:: mem:: size_of :: < ( usize , ( ) ) > ( ) + 1 ;
6735
+
6736
+ // Accounting for some misc. padding that's likely in the allocation
6737
+ // due to rounding to group width, etc.
6738
+ let overhead = 3 * core:: mem:: size_of :: < usize > ( ) ;
6739
+ let num_buckets = ( alloc. page_size - overhead) / rough_bucket_size;
6740
+ // Buckets are always powers of 2.
6741
+ let min_elems = prev_pow2 ( num_buckets) ;
6742
+ // Real load-factor is 7/8, but this is a lower estimation, so 1/2.
6743
+ let min_capacity = min_elems >> 1 ;
6744
+ let capacity = map. capacity ( ) ;
6745
+ assert ! (
6746
+ capacity >= min_capacity,
6747
+ "failed: {capacity} >= {min_capacity}"
6748
+ ) ;
6749
+
6750
+ // Fill it up.
6751
+ for i in 0 ..capacity {
6752
+ map. insert ( i, ( ) ) ;
6753
+ }
6754
+ // Capacity should not have changed and it should be full.
6755
+ assert_eq ! ( capacity, map. len( ) ) ;
6756
+ assert_eq ! ( capacity, map. capacity( ) ) ;
6757
+
6758
+ // Alright, make it grow.
6759
+ map. insert ( capacity, ( ) ) ;
6760
+ assert ! (
6761
+ capacity < map. capacity( ) ,
6762
+ "failed: {capacity} < {}" ,
6763
+ map. capacity( )
6764
+ ) ;
6765
+ }
6766
+ }
0 commit comments