22// SPDX-FileCopyrightText: Copyright the Vortex contributors
33
44//! Allocator-backed storage for Vortex buffers.
5+ //!
6+ //! Each allocation retains its base pointer and original layout through slicing and ownership
7+ //! transfers. Buffer data pointers and logical alignments do not change the layout used to free it.
58
69use std:: alloc:: Layout ;
710use std:: any:: Any ;
@@ -22,7 +25,7 @@ use crate::BufferMut;
2225
2326/// An allocator that can back a Vortex buffer.
2427///
25- /// Vortex over-allocates raw storage and aligns the buffer within it .
28+ /// Buffer allocations pass their byte capacity and effective alignment through [`Layout`] .
2629pub trait BufferAllocator : Allocator + Debug + Send + Sync + ' static { }
2730
2831impl < A > BufferAllocator for A where A : Allocator + Debug + Send + Sync + ' static { }
@@ -235,7 +238,9 @@ unsafe impl Allocator for StaticBufferAllocator {
235238static STATIC_ALLOCATOR : BufferAllocatorRef = BufferAllocatorRef ( None ) ;
236239
237240pub ( crate ) struct Allocation {
241+ /// Allocation base, or an aligned dangling pointer when the layout has zero size.
238242 ptr : NonNull < u8 > ,
243+ /// Layout used to allocate this block. Allocator excess is not exposed as buffer capacity.
239244 layout : Layout ,
240245 allocator : BufferAllocatorRef ,
241246}
@@ -318,6 +323,7 @@ impl Allocation {
318323 }
319324
320325 pub ( crate ) fn grow ( & mut self , new_layout : Layout ) {
326+ debug_assert ! ( new_layout. size( ) >= self . layout. size( ) ) ;
321327 let allocation = if self . layout . size ( ) == 0 {
322328 self . allocator . allocate ( new_layout)
323329 } else {
@@ -363,175 +369,3 @@ impl BufferBacking {
363369 }
364370 }
365371}
366-
367- #[ cfg( test) ]
368- mod tests {
369- use std:: alloc:: Layout ;
370- use std:: ptr:: NonNull ;
371- use std:: sync:: Arc ;
372- use std:: sync:: atomic:: AtomicUsize ;
373- use std:: sync:: atomic:: Ordering ;
374-
375- use allocator_api2:: alloc:: AllocError ;
376- use allocator_api2:: alloc:: Allocator ;
377- use allocator_api2:: alloc:: Global ;
378- use rstest:: rstest;
379- use vortex_error:: VortexResult ;
380- use vortex_error:: vortex_err;
381-
382- use crate :: Alignment ;
383- use crate :: BufferAllocatorRef ;
384- use crate :: BufferMut ;
385-
386- #[ derive( Clone , Debug , Default ) ]
387- struct TrackingAllocator {
388- state : Arc < TrackingState > ,
389- }
390-
391- #[ derive( Debug , Default ) ]
392- struct TrackingState {
393- allocations : AtomicUsize ,
394- deallocations : AtomicUsize ,
395- grows : AtomicUsize ,
396- alignment : AtomicUsize ,
397- }
398-
399- // SAFETY: this forwards all memory operations to Global and only records call metadata.
400- unsafe impl Allocator for TrackingAllocator {
401- fn allocate ( & self , layout : Layout ) -> Result < NonNull < [ u8 ] > , AllocError > {
402- self . state . allocations . fetch_add ( 1 , Ordering :: Relaxed ) ;
403- self . state
404- . alignment
405- . store ( layout. align ( ) , Ordering :: Relaxed ) ;
406- Global . allocate ( layout)
407- }
408-
409- unsafe fn deallocate ( & self , ptr : NonNull < u8 > , layout : Layout ) {
410- self . state . deallocations . fetch_add ( 1 , Ordering :: Relaxed ) ;
411- // SAFETY: the caller passes the pointer and layout returned by Global.
412- unsafe { Global . deallocate ( ptr, layout) }
413- }
414-
415- unsafe fn grow (
416- & self ,
417- ptr : NonNull < u8 > ,
418- old_layout : Layout ,
419- new_layout : Layout ,
420- ) -> Result < NonNull < [ u8 ] > , AllocError > {
421- self . state . grows . fetch_add ( 1 , Ordering :: Relaxed ) ;
422- // SAFETY: the caller upholds the Allocator contract.
423- unsafe { Global . grow ( ptr, old_layout, new_layout) }
424- }
425- }
426-
427- #[ test]
428- fn allocator_identity ( ) {
429- let static_allocator = BufferAllocatorRef :: statically_allocated ( ) ;
430- assert ! ( static_allocator. ptr_eq( & BufferAllocatorRef :: statically_allocated( ) ) ) ;
431-
432- let custom_allocator = BufferAllocatorRef :: new ( TrackingAllocator :: default ( ) ) ;
433- assert ! ( custom_allocator. ptr_eq( & custom_allocator. clone( ) ) ) ;
434- assert ! ( !custom_allocator. ptr_eq( & static_allocator) ) ;
435- assert ! ( !custom_allocator. ptr_eq( & BufferAllocatorRef :: new( TrackingAllocator :: default ( ) ) ) ) ;
436- }
437-
438- #[ test]
439- fn allocation_lives_until_last_view ( ) {
440- let allocator = TrackingAllocator :: default ( ) ;
441- let state = Arc :: clone ( & allocator. state ) ;
442- let buffer = BufferAllocatorRef :: new ( allocator)
443- . copy_from ( [ 1u32 , 2 , 3 , 4 ] )
444- . freeze ( ) ;
445- let view = buffer. slice ( 0 ..2 ) ;
446-
447- assert_eq ! ( state. allocations. load( Ordering :: Relaxed ) , 1 ) ;
448- assert_eq ! (
449- state. alignment. load( Ordering :: Relaxed ) ,
450- Alignment :: of:: <u8 >( ) . as_usize( )
451- ) ;
452- drop ( buffer) ;
453- assert_eq ! ( state. deallocations. load( Ordering :: Relaxed ) , 0 ) ;
454- drop ( view) ;
455- assert_eq ! ( state. deallocations. load( Ordering :: Relaxed ) , 1 ) ;
456- }
457-
458- #[ rstest]
459- fn buffer_growth_uses_allocator_grow ( #[ values( 4 , 64 , 4096 ) ] alignment : usize ) {
460- let allocator = TrackingAllocator :: default ( ) ;
461- let state = Arc :: clone ( & allocator. state ) ;
462- let alignment = Alignment :: new ( alignment) ;
463- let mut buffer =
464- BufferAllocatorRef :: new ( allocator) . with_capacity_aligned :: < u32 > ( 1 , alignment) ;
465- let initial_capacity = buffer. capacity ( ) ;
466- buffer. extend ( std:: iter:: repeat_n ( 7 , initial_capacity) ) ;
467-
468- buffer. push ( u32:: MAX ) ;
469- assert ! ( alignment. is_ptr_aligned( buffer. as_ptr( ) ) ) ;
470-
471- assert_eq ! ( & buffer[ ..initial_capacity] , vec![ 7 ; initial_capacity] ) ;
472- assert_eq ! ( buffer[ initial_capacity] , u32 :: MAX ) ;
473- assert_eq ! ( state. allocations. load( Ordering :: Relaxed ) , 1 ) ;
474- assert_eq ! ( state. deallocations. load( Ordering :: Relaxed ) , 0 ) ;
475- assert_eq ! ( state. grows. load( Ordering :: Relaxed ) , 1 ) ;
476-
477- drop ( buffer) ;
478- assert_eq ! ( state. deallocations. load( Ordering :: Relaxed ) , 1 ) ;
479- }
480-
481- #[ test]
482- fn zero_capacity_does_not_allocate ( ) {
483- let allocator = TrackingAllocator :: default ( ) ;
484- let state = Arc :: clone ( & allocator. state ) ;
485- let mut buffer = BufferAllocatorRef :: new ( allocator) . with_capacity :: < u32 > ( 0 ) ;
486-
487- assert_eq ! ( buffer. capacity( ) , 0 ) ;
488- assert ! ( Alignment :: DEFAULT_ALIGNMENT . is_offset_aligned( buffer. as_ptr( ) . addr( ) ) ) ;
489- assert_eq ! ( state. allocations. load( Ordering :: Relaxed ) , 0 ) ;
490-
491- buffer. push ( 42 ) ;
492-
493- assert_eq ! ( buffer. as_slice( ) , [ 42 ] ) ;
494- assert_eq ! ( state. allocations. load( Ordering :: Relaxed ) , 1 ) ;
495- assert_eq ! ( state. grows. load( Ordering :: Relaxed ) , 0 ) ;
496- }
497-
498- #[ test]
499- fn empty_buffers_preserve_allocator_without_allocating ( ) -> VortexResult < ( ) > {
500- let allocator = TrackingAllocator :: default ( ) ;
501- let state = Arc :: clone ( & allocator. state ) ;
502- let allocator = BufferAllocatorRef :: new ( allocator) ;
503- let buffer = BufferMut :: < u32 > :: zeroed_in ( 0 , allocator. clone ( ) ) ;
504- let buffer = buffer. freeze ( ) ;
505- let copy = buffer. clone ( ) . into_mut ( ) ;
506- assert ! ( copy. allocator( ) . ptr_eq( & allocator) ) ;
507- let mut buffer = buffer
508- . try_into_mut ( )
509- . map_err ( |_| vortex_err ! ( "unique buffer" ) ) ?;
510- buffer. reserve ( 0 ) ;
511- assert ! ( buffer. is_empty( ) ) ;
512- assert ! ( buffer. allocator( ) . ptr_eq( & allocator) ) ;
513- drop ( ( copy, buffer) ) ;
514- assert_eq ! ( state. allocations. load( Ordering :: Relaxed ) , 0 ) ;
515- assert_eq ! ( state. grows. load( Ordering :: Relaxed ) , 0 ) ;
516- assert_eq ! ( state. deallocations. load( Ordering :: Relaxed ) , 0 ) ;
517- Ok ( ( ) )
518- }
519-
520- #[ test]
521- fn shared_into_mut_preserves_allocator ( ) {
522- let allocator = TrackingAllocator :: default ( ) ;
523- let state = Arc :: clone ( & allocator. state ) ;
524- let allocator = BufferAllocatorRef :: new ( allocator) ;
525- let original = allocator. copy_from ( [ 1u32 , 2 , 3 ] ) . freeze ( ) ;
526- let mut copy = original. clone ( ) . into_mut ( ) ;
527- assert ! ( copy. allocator( ) . ptr_eq( & allocator) ) ;
528- copy[ 0 ] = 42 ;
529- assert_eq ! ( original. as_slice( ) , [ 1 , 2 , 3 ] ) ;
530- assert_eq ! ( copy. as_slice( ) , [ 42 , 2 , 3 ] ) ;
531- assert_eq ! ( state. allocations. load( Ordering :: Relaxed ) , 2 ) ;
532- drop ( copy) ;
533- assert_eq ! ( state. deallocations. load( Ordering :: Relaxed ) , 1 ) ;
534- drop ( original) ;
535- assert_eq ! ( state. deallocations. load( Ordering :: Relaxed ) , 2 ) ;
536- }
537- }
0 commit comments