@@ -6631,3 +6631,136 @@ mod test_map {
66316631 ) ;
66326632 }
66336633}
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