@@ -6514,3 +6514,123 @@ mod test_map {
6514
6514
) ;
6515
6515
}
6516
6516
}
6517
+
6518
+ #[ cfg( all( test, unix) ) ]
6519
+ mod test_map_with_mmap_allocations {
6520
+ use super :: HashMap ;
6521
+ use crate :: raw:: prev_pow2;
6522
+ use allocator_api2:: alloc:: { AllocError , Allocator } ;
6523
+ use core:: alloc:: Layout ;
6524
+ use core:: ptr:: { null_mut, NonNull } ;
6525
+
6526
+ /// This is not a production quality allocator, just good enough for
6527
+ /// some basic tests.
6528
+ #[ derive( Clone , Copy , Debug ) ]
6529
+ struct MmapAllocator {
6530
+ /// Guarantee this is a power of 2.
6531
+ page_size : usize ,
6532
+ }
6533
+
6534
+ impl MmapAllocator {
6535
+ fn new ( ) -> Result < Self , AllocError > {
6536
+ let result = unsafe { libc:: sysconf ( libc:: _SC_PAGESIZE) } ;
6537
+ if result < 1 {
6538
+ return Err ( AllocError ) ;
6539
+ }
6540
+
6541
+ let page_size = result as usize ;
6542
+ if !page_size. is_power_of_two ( ) {
6543
+ Err ( AllocError )
6544
+ } else {
6545
+ Ok ( Self { page_size } )
6546
+ }
6547
+ }
6548
+
6549
+ fn fit_to_page_size ( & self , n : usize ) -> Result < usize , AllocError > {
6550
+ // If n=0, give a single page (wasteful, I know).
6551
+ let n = if n == 0 { self . page_size } else { n } ;
6552
+
6553
+ match n & ( self . page_size - 1 ) {
6554
+ 0 => Ok ( n) ,
6555
+ rem => n. checked_add ( self . page_size - rem) . ok_or ( AllocError ) ,
6556
+ }
6557
+ }
6558
+ }
6559
+
6560
+ unsafe impl Allocator for MmapAllocator {
6561
+ fn allocate ( & self , layout : Layout ) -> Result < NonNull < [ u8 ] > , AllocError > {
6562
+ if layout. align ( ) > self . page_size {
6563
+ return Err ( AllocError ) ;
6564
+ }
6565
+
6566
+ let null = null_mut ( ) ;
6567
+ let len = self . fit_to_page_size ( layout. size ( ) ) ? as libc:: size_t ;
6568
+ let prot = libc:: PROT_READ | libc:: PROT_WRITE ;
6569
+ let flags = libc:: MAP_PRIVATE | libc:: MAP_ANON ;
6570
+ let addr = unsafe { libc:: mmap ( null, len, prot, flags, -1 , 0 ) } ;
6571
+
6572
+ // mmap returns MAP_FAILED on failure, not Null.
6573
+ if addr == libc:: MAP_FAILED {
6574
+ return Err ( AllocError ) ;
6575
+ }
6576
+
6577
+ match NonNull :: new ( addr. cast ( ) ) {
6578
+ Some ( addr) => Ok ( NonNull :: slice_from_raw_parts ( addr, len) ) ,
6579
+
6580
+ // This branch shouldn't be taken in practice, but since we
6581
+ // cannot return null as a valid pointer in our type system,
6582
+ // we attempt to handle it.
6583
+ None => {
6584
+ _ = unsafe { libc:: munmap ( addr, len) } ;
6585
+ Err ( AllocError )
6586
+ }
6587
+ }
6588
+ }
6589
+
6590
+ unsafe fn deallocate ( & self , ptr : NonNull < u8 > , layout : Layout ) {
6591
+ // If they allocated it with this layout, it must round correctly.
6592
+ let size = self . fit_to_page_size ( layout. size ( ) ) . unwrap ( ) ;
6593
+ let _result = libc:: munmap ( ptr. as_ptr ( ) . cast ( ) , size) ;
6594
+ debug_assert_eq ! ( 0 , _result)
6595
+ }
6596
+ }
6597
+
6598
+ #[ test]
6599
+ fn test_tiny_allocation_gets_rounded_to_page_size ( ) {
6600
+ let alloc = MmapAllocator :: new ( ) . unwrap ( ) ;
6601
+ let mut map: HashMap < usize , ( ) , _ , _ > = HashMap :: with_capacity_in ( 1 , alloc) ;
6602
+
6603
+ // Size of an element plus its control byte.
6604
+ let rough_bucket_size = core:: mem:: size_of :: < ( usize , ( ) ) > ( ) + 1 ;
6605
+
6606
+ // Accounting for some misc. padding that's likely in the allocation
6607
+ // due to rounding to group width, etc.
6608
+ let overhead = 3 * core:: mem:: size_of :: < usize > ( ) ;
6609
+ let num_buckets = ( alloc. page_size - overhead) / rough_bucket_size;
6610
+ // Buckets are always powers of 2.
6611
+ let min_elems = prev_pow2 ( num_buckets) ;
6612
+ // Real load-factor is 7/8, but this is a lower estimation, so 1/2.
6613
+ let min_capacity = min_elems >> 1 ;
6614
+ let capacity = map. capacity ( ) ;
6615
+ assert ! (
6616
+ capacity >= min_capacity,
6617
+ "failed: {capacity} >= {min_capacity}"
6618
+ ) ;
6619
+
6620
+ // Fill it up.
6621
+ for i in 0 ..capacity {
6622
+ map. insert ( i, ( ) ) ;
6623
+ }
6624
+ // Capacity should not have changed and it should be full.
6625
+ assert_eq ! ( capacity, map. len( ) ) ;
6626
+ assert_eq ! ( capacity, map. capacity( ) ) ;
6627
+
6628
+ // Alright, make it grow.
6629
+ map. insert ( capacity, ( ) ) ;
6630
+ assert ! (
6631
+ capacity < map. capacity( ) ,
6632
+ "failed: {capacity} < {}" ,
6633
+ map. capacity( )
6634
+ ) ;
6635
+ }
6636
+ }
0 commit comments