@@ -33,16 +33,17 @@ mod table {
3333 extern crate libc;
3434
3535 use std:: clone:: Clone ;
36+ use std:: cmp;
3637 use std:: cmp:: Eq ;
3738 use std:: hash:: { Hash , Hasher } ;
3839 use std:: kinds:: marker;
39- use std:: num:: CheckedMul ;
40+ use std:: num:: { CheckedMul , is_power_of_two } ;
4041 use std:: option:: { Option , Some , None } ;
4142 use std:: prelude:: Drop ;
4243 use std:: ptr;
4344 use std:: ptr:: RawPtr ;
4445 use std:: rt:: global_heap;
45- use std:: intrinsics:: { size_of, transmute, move_val_init} ;
46+ use std:: intrinsics:: { size_of, min_align_of , transmute, move_val_init} ;
4647 use std:: iter:: { Iterator , range_step_inclusive} ;
4748
4849 static EMPTY_BUCKET : u64 = 0u64 ;
@@ -163,6 +164,42 @@ mod table {
163164 }
164165 }
165166
167+ fn round_up_to_next ( unrounded : uint , target_alignment : uint ) -> uint {
168+ assert ! ( is_power_of_two( target_alignment) ) ;
169+ ( unrounded + target_alignment - 1 ) & !( target_alignment - 1 )
170+ }
171+
172+ #[ test]
173+ fn test_rounding ( ) {
174+ assert ! ( round_up_to_next( 0 , 4 ) == 0 ) ;
175+ assert ! ( round_up_to_next( 1 , 4 ) == 4 ) ;
176+ assert ! ( round_up_to_next( 2 , 4 ) == 4 ) ;
177+ assert ! ( round_up_to_next( 3 , 4 ) == 4 ) ;
178+ assert ! ( round_up_to_next( 4 , 4 ) == 4 ) ;
179+ assert ! ( round_up_to_next( 5 , 4 ) == 8 ) ;
180+ }
181+
182+ // Returns a tuple of (minimum required malloc alignment, hash_offset,
183+ // key_offset, val_offset, array_size), from the start of a mallocated array.
184+ fn calculate_offsets (
185+ hash_size : uint , hash_align : uint ,
186+ keys_size : uint , keys_align : uint ,
187+ vals_size : uint , vals_align : uint ) -> ( uint , uint , uint , uint , uint ) {
188+
189+ let hash_offset = 0 ;
190+ let end_of_hashes = hash_offset + hash_size;
191+
192+ let keys_offset = round_up_to_next ( end_of_hashes, keys_align) ;
193+ let end_of_keys = keys_offset + keys_size;
194+
195+ let vals_offset = round_up_to_next ( end_of_keys, vals_align) ;
196+ let end_of_vals = vals_offset + vals_size;
197+
198+ let min_align = cmp:: max ( hash_align, cmp:: max ( keys_align, vals_align) ) ;
199+
200+ ( min_align, hash_offset, keys_offset, vals_offset, end_of_vals)
201+ }
202+
166203 impl < K , V > RawTable < K , V > {
167204
168205 /// Does not initialize the buckets. The caller should ensure they,
@@ -175,32 +212,31 @@ mod table {
175212 let vals_size =
176213 capacity. checked_mul ( & size_of :: < V > ( ) ) . expect ( "capacity overflow" ) ;
177214
178- /*
179- The following code was my first pass at making RawTable only
180- allocate a single buffer, since that's all it needs. There's
181- no logical reason for this to require three calls to malloc.
182-
183- However, I'm not convinced the code below is correct. If you
184- want to take a stab at it, please do! The alignment is
185- especially tricky to get right, especially if you need more
186- alignment than malloc guarantees.
187-
188- let hashes_offset = 0;
189- let keys_offset = align_size(hashes_offset + hashes_size, keys_align);
190- let vals_offset = align_size(keys_offset + keys_size, vals_align);
191- let end = vals_offset + vals_size;
192-
193- let buffer = global_heap::malloc_raw(end);
194-
195- let hashes = buffer.offset(hashes_offset) as *mut u64;
196- let keys = buffer.offset(keys_offset) as *mut K;
197- let vals = buffer.offset(vals_offset) as *mut V;
198-
199- */
200-
201- let hashes = global_heap:: malloc_raw ( hashes_size) as * mut u64 ;
202- let keys = global_heap:: malloc_raw ( keys_size) as * mut K ;
203- let vals = global_heap:: malloc_raw ( vals_size) as * mut V ;
215+ // Allocating hashmaps is a little tricky. We need to allocate three
216+ // arrays here, but since we know their sizes and alignments up front,
217+ // we could theoretically allocate only a single array, and then have
218+ // the subarrays just point into it.
219+ //
220+ // This is great in theory, but in practice getting the alignment
221+ // right is a little subtle. Therefore, calculating offsets has been
222+ // factored out into a different function.
223+ let ( malloc_alignment, hash_offset, keys_offset, vals_offset, size) =
224+ calculate_offsets (
225+ hashes_size, min_align_of :: < u64 > ( ) ,
226+ keys_size, min_align_of :: < K > ( ) ,
227+ vals_size, min_align_of :: < V > ( ) ) ;
228+
229+ let buffer = global_heap:: malloc_raw ( size) as * mut u8 ;
230+
231+ // FIXME #13094: If malloc was not at as aligned as we expected,
232+ // our offset calculations are just plain wrong. We could support
233+ // any alignment if we switched from `malloc` to `posix_memalign`.
234+ assert ! ( round_up_to_next( buffer as uint, malloc_alignment)
235+ == ( buffer as uint) ) ;
236+
237+ let hashes = buffer. offset ( hash_offset as int ) as * mut u64 ;
238+ let keys = buffer. offset ( keys_offset as int ) as * mut K ;
239+ let vals = buffer. offset ( vals_offset as int ) as * mut V ;
204240
205241 RawTable {
206242 capacity : capacity,
@@ -513,9 +549,9 @@ mod table {
513549 assert ! ( self . size == 0 ) ;
514550
515551 unsafe {
516- libc:: free ( self . vals as * mut libc:: c_void ) ;
517- libc:: free ( self . keys as * mut libc:: c_void ) ;
518552 libc:: free ( self . hashes as * mut libc:: c_void ) ;
553+ // Remember how everything was allocated out of one buffer
554+ // during initialization? We only need one call to free here.
519555 }
520556 }
521557 }
0 commit comments