Skip to content

Commit 22ef33a

Browse files
committed
Pass buffer capacity and alignment to the allocator
Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
1 parent 402fd1d commit 22ef33a

3 files changed

Lines changed: 455 additions & 246 deletions

File tree

‎vortex-buffer/src/allocation.rs‎

Lines changed: 7 additions & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
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
69
use std::alloc::Layout;
710
use 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`].
2629
pub trait BufferAllocator: Allocator + Debug + Send + Sync + 'static {}
2730

2831
impl<A> BufferAllocator for A where A: Allocator + Debug + Send + Sync + 'static {}
@@ -235,7 +238,9 @@ unsafe impl Allocator for StaticBufferAllocator {
235238
static STATIC_ALLOCATOR: BufferAllocatorRef = BufferAllocatorRef(None);
236239

237240
pub(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

Comments
 (0)