1use std::alloc::Layout;
7use std::any::Any;
8use std::fmt;
9use std::fmt::Debug;
10use std::mem::ManuallyDrop;
11use std::ptr::NonNull;
12use std::sync::Arc;
13
14use allocator_api2::alloc::AllocError;
15use allocator_api2::alloc::Allocator;
16use allocator_api2::alloc::Global;
17use allocator_api2::alloc::handle_alloc_error;
18use vortex_error::VortexExpect;
19
20use crate::Alignment;
21use crate::BufferMut;
22
23pub trait BufferAllocator: Allocator + Debug + Send + Sync + 'static {}
27
28impl<A> BufferAllocator for A where A: Allocator + Debug + Send + Sync + 'static {}
29
30#[derive(Clone)]
35pub struct BufferAllocatorRef(
36 Option<Arc<dyn BufferAllocator>>,
39);
40
41impl BufferAllocatorRef {
42 pub fn new(allocator: impl BufferAllocator) -> Self {
44 Self(Some(Arc::new(allocator)))
45 }
46
47 pub fn statically_allocated() -> Self {
49 Self(None)
50 }
51
52 pub fn static_ref() -> &'static Self {
54 &STATIC_ALLOCATOR
55 }
56
57 pub(crate) fn is_statically_allocated(&self) -> bool {
58 self.0.is_none()
59 }
60
61 pub fn ptr_eq(&self, other: &Self) -> bool {
63 match (&self.0, &other.0) {
64 (None, None) => true,
65 (Some(lhs), Some(rhs)) => Arc::ptr_eq(lhs, rhs),
66 _ => false,
67 }
68 }
69
70 pub fn with_capacity<T>(&self, capacity: usize) -> BufferMut<T> {
72 BufferMut::with_capacity_in(capacity, self.clone())
73 }
74
75 pub fn with_capacity_aligned<T>(&self, capacity: usize, alignment: Alignment) -> BufferMut<T> {
77 BufferMut::with_capacity_aligned_in(capacity, alignment, self.clone())
78 }
79
80 pub fn zeroed<T>(&self, len: usize) -> BufferMut<T> {
82 BufferMut::zeroed_in(len, self.clone())
83 }
84
85 pub fn copy_from<T>(&self, values: impl AsRef<[T]>) -> BufferMut<T> {
87 BufferMut::copy_from_in(values, self.clone())
88 }
89}
90
91impl Debug for BufferAllocatorRef {
92 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93 match &self.0 {
94 Some(allocator) => allocator.fmt(f),
95 None => StaticBufferAllocator.fmt(f),
96 }
97 }
98}
99
100unsafe impl Allocator for BufferAllocatorRef {
102 fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
103 match &self.0 {
104 Some(allocator) => allocator.allocate(layout),
105 None => Global.allocate(layout),
106 }
107 }
108
109 fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
110 match &self.0 {
111 Some(allocator) => allocator.allocate_zeroed(layout),
112 None => Global.allocate_zeroed(layout),
113 }
114 }
115
116 unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
117 match &self.0 {
119 Some(allocator) => unsafe { allocator.deallocate(ptr, layout) },
120 None => unsafe { Global.deallocate(ptr, layout) },
121 }
122 }
123
124 unsafe fn grow(
125 &self,
126 ptr: NonNull<u8>,
127 old_layout: Layout,
128 new_layout: Layout,
129 ) -> Result<NonNull<[u8]>, AllocError> {
130 match &self.0 {
132 Some(allocator) => unsafe { allocator.grow(ptr, old_layout, new_layout) },
133 None => unsafe { Global.grow(ptr, old_layout, new_layout) },
134 }
135 }
136
137 unsafe fn grow_zeroed(
138 &self,
139 ptr: NonNull<u8>,
140 old_layout: Layout,
141 new_layout: Layout,
142 ) -> Result<NonNull<[u8]>, AllocError> {
143 match &self.0 {
145 Some(allocator) => unsafe { allocator.grow_zeroed(ptr, old_layout, new_layout) },
146 None => unsafe { Global.grow_zeroed(ptr, old_layout, new_layout) },
147 }
148 }
149
150 unsafe fn shrink(
151 &self,
152 ptr: NonNull<u8>,
153 old_layout: Layout,
154 new_layout: Layout,
155 ) -> Result<NonNull<[u8]>, AllocError> {
156 match &self.0 {
158 Some(allocator) => unsafe { allocator.shrink(ptr, old_layout, new_layout) },
159 None => unsafe { Global.shrink(ptr, old_layout, new_layout) },
160 }
161 }
162}
163
164#[derive(Clone, Copy, Debug, Default)]
166pub struct StaticBufferAllocator;
167
168impl StaticBufferAllocator {
169 pub fn with_capacity<T>(capacity: usize) -> BufferMut<T> {
171 BufferMut::with_capacity(capacity)
172 }
173
174 pub fn with_capacity_aligned<T>(capacity: usize, alignment: Alignment) -> BufferMut<T> {
176 BufferMut::with_capacity_aligned(capacity, alignment)
177 }
178
179 pub fn zeroed<T>(len: usize) -> BufferMut<T> {
181 BufferMut::zeroed(len)
182 }
183
184 pub fn copy_from<T>(values: impl AsRef<[T]>) -> BufferMut<T> {
186 BufferMut::copy_from(values)
187 }
188}
189
190unsafe impl Allocator for StaticBufferAllocator {
192 fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
193 Global.allocate(layout)
194 }
195
196 fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
197 Global.allocate_zeroed(layout)
198 }
199
200 unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
201 unsafe { Global.deallocate(ptr, layout) }
203 }
204
205 unsafe fn grow(
206 &self,
207 ptr: NonNull<u8>,
208 old_layout: Layout,
209 new_layout: Layout,
210 ) -> Result<NonNull<[u8]>, AllocError> {
211 unsafe { Global.grow(ptr, old_layout, new_layout) }
213 }
214
215 unsafe fn grow_zeroed(
216 &self,
217 ptr: NonNull<u8>,
218 old_layout: Layout,
219 new_layout: Layout,
220 ) -> Result<NonNull<[u8]>, AllocError> {
221 unsafe { Global.grow_zeroed(ptr, old_layout, new_layout) }
223 }
224
225 unsafe fn shrink(
226 &self,
227 ptr: NonNull<u8>,
228 old_layout: Layout,
229 new_layout: Layout,
230 ) -> Result<NonNull<[u8]>, AllocError> {
231 unsafe { Global.shrink(ptr, old_layout, new_layout) }
233 }
234}
235
236static STATIC_ALLOCATOR: BufferAllocatorRef = BufferAllocatorRef(None);
237
238pub(crate) struct Allocation {
239 ptr: NonNull<u8>,
240 layout: Layout,
241 allocator: BufferAllocatorRef,
242}
243
244unsafe impl Send for Allocation {}
246unsafe impl Sync for Allocation {}
248
249impl Allocation {
250 pub(crate) fn allocate(layout: Layout, allocator: BufferAllocatorRef) -> Self {
251 Self::allocate_impl(layout, allocator, false)
252 }
253
254 pub(crate) fn allocate_zeroed(layout: Layout, allocator: BufferAllocatorRef) -> Self {
255 Self::allocate_impl(layout, allocator, true)
256 }
257
258 pub(crate) fn from_vec<T>(vec: Vec<T>) -> Self {
259 assert!(!std::mem::needs_drop::<T>());
260
261 let mut vec = ManuallyDrop::new(vec);
262 let layout = Layout::array::<T>(vec.capacity())
263 .unwrap_or_else(|_| unreachable!("a Vec capacity always has a valid layout"));
264 let ptr = NonNull::new(vec.as_mut_ptr().cast())
265 .vortex_expect("a Vec always has a non-null pointer");
266
267 Self {
268 ptr,
269 layout,
270 allocator: BufferAllocatorRef::statically_allocated(),
271 }
272 }
273
274 fn allocate_impl(layout: Layout, allocator: BufferAllocatorRef, zeroed: bool) -> Self {
275 if layout.size() == 0 {
276 return Self {
277 ptr: layout.dangling_ptr(),
278 layout,
279 allocator,
280 };
281 }
282
283 let allocation = if zeroed {
284 allocator.allocate_zeroed(layout)
285 } else {
286 allocator.allocate(layout)
287 }
288 .unwrap_or_else(|_| handle_alloc_error(layout));
289
290 Self {
291 ptr: allocation.cast(),
292 layout,
293 allocator,
294 }
295 }
296
297 #[allow(clippy::inline_always)]
298 #[inline(always)]
299 pub(crate) fn ptr(&self) -> NonNull<u8> {
300 self.ptr
301 }
302
303 #[allow(clippy::inline_always)]
304 #[inline(always)]
305 pub(crate) fn size(&self) -> usize {
306 self.layout.size()
307 }
308
309 #[allow(clippy::inline_always)]
310 #[inline(always)]
311 pub(crate) fn alignment(&self) -> usize {
312 self.layout.align()
313 }
314
315 #[allow(clippy::inline_always)]
316 #[inline(always)]
317 pub(crate) fn allocator(&self) -> &BufferAllocatorRef {
318 &self.allocator
319 }
320
321 pub(crate) fn grow(&mut self, new_layout: Layout) {
322 let allocation = if self.layout.size() == 0 {
323 self.allocator.allocate(new_layout)
324 } else {
325 unsafe { self.allocator.grow(self.ptr, self.layout, new_layout) }
328 }
329 .unwrap_or_else(|_| handle_alloc_error(new_layout));
330 self.ptr = allocation.cast();
331 self.layout = new_layout;
332 }
333}
334
335impl Drop for Allocation {
336 fn drop(&mut self) {
337 if self.layout.size() == 0 {
338 return;
339 }
340 unsafe { self.allocator.deallocate(self.ptr, self.layout) }
342 }
343}
344
345pub(crate) enum BufferBacking {
346 Owned(Allocation),
347 Bytes(bytes::Bytes),
348 #[cfg(feature = "arrow")]
349 Arrow(arrow_buffer::Buffer),
350 External {
351 _owner: Box<dyn Any + Send + Sync>,
352 },
353}
354
355impl BufferBacking {
356 #[allow(clippy::inline_always)]
357 #[inline(always)]
358 pub(crate) fn allocator(&self) -> &BufferAllocatorRef {
359 match self {
360 Self::Owned(allocation) => allocation.allocator(),
361 Self::Bytes(_) | Self::External { .. } => &STATIC_ALLOCATOR,
362 #[cfg(feature = "arrow")]
363 Self::Arrow(_) => &STATIC_ALLOCATOR,
364 }
365 }
366}
367
368#[cfg(test)]
369mod tests {
370 use std::alloc::Layout;
371 use std::ptr::NonNull;
372 use std::sync::Arc;
373 use std::sync::atomic::AtomicUsize;
374 use std::sync::atomic::Ordering;
375
376 use allocator_api2::alloc::AllocError;
377 use allocator_api2::alloc::Allocator;
378 use allocator_api2::alloc::Global;
379 use rstest::rstest;
380 use vortex_error::VortexResult;
381 use vortex_error::vortex_err;
382
383 use crate::Alignment;
384 use crate::BufferAllocatorRef;
385 use crate::BufferMut;
386
387 #[derive(Clone, Debug, Default)]
388 struct TrackingAllocator {
389 state: Arc<TrackingState>,
390 }
391
392 #[derive(Debug, Default)]
393 struct TrackingState {
394 allocations: AtomicUsize,
395 deallocations: AtomicUsize,
396 grows: AtomicUsize,
397 alignment: AtomicUsize,
398 }
399
400 unsafe impl Allocator for TrackingAllocator {
402 fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
403 self.state.allocations.fetch_add(1, Ordering::Relaxed);
404 self.state
405 .alignment
406 .store(layout.align(), Ordering::Relaxed);
407 Global.allocate(layout)
408 }
409
410 unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
411 self.state.deallocations.fetch_add(1, Ordering::Relaxed);
412 unsafe { Global.deallocate(ptr, layout) }
414 }
415
416 unsafe fn grow(
417 &self,
418 ptr: NonNull<u8>,
419 old_layout: Layout,
420 new_layout: Layout,
421 ) -> Result<NonNull<[u8]>, AllocError> {
422 self.state.grows.fetch_add(1, Ordering::Relaxed);
423 unsafe { Global.grow(ptr, old_layout, new_layout) }
425 }
426 }
427
428 #[test]
429 fn allocator_identity() {
430 let static_allocator = BufferAllocatorRef::statically_allocated();
431 assert!(static_allocator.ptr_eq(&BufferAllocatorRef::statically_allocated()));
432
433 let custom_allocator = BufferAllocatorRef::new(TrackingAllocator::default());
434 assert!(custom_allocator.ptr_eq(&custom_allocator.clone()));
435 assert!(!custom_allocator.ptr_eq(&static_allocator));
436 assert!(!custom_allocator.ptr_eq(&BufferAllocatorRef::new(TrackingAllocator::default())));
437 }
438
439 #[test]
440 fn allocation_lives_until_last_view() {
441 let allocator = TrackingAllocator::default();
442 let state = Arc::clone(&allocator.state);
443 let buffer = BufferAllocatorRef::new(allocator)
444 .copy_from([1u32, 2, 3, 4])
445 .freeze();
446 let view = buffer.slice(0..2);
447
448 assert_eq!(state.allocations.load(Ordering::Relaxed), 1);
449 assert_eq!(
450 state.alignment.load(Ordering::Relaxed),
451 Alignment::of::<u8>().as_usize()
452 );
453 drop(buffer);
454 assert_eq!(state.deallocations.load(Ordering::Relaxed), 0);
455 drop(view);
456 assert_eq!(state.deallocations.load(Ordering::Relaxed), 1);
457 }
458
459 #[rstest]
460 fn buffer_growth_uses_allocator_grow(#[values(4, 64, 4096)] alignment: usize) {
461 let allocator = TrackingAllocator::default();
462 let state = Arc::clone(&allocator.state);
463 let alignment = Alignment::new(alignment);
464 let mut buffer =
465 BufferAllocatorRef::new(allocator).with_capacity_aligned::<u32>(1, alignment);
466 let initial_capacity = buffer.capacity();
467 buffer.extend(std::iter::repeat_n(7, initial_capacity));
468
469 buffer.push(u32::MAX);
470 assert!(alignment.is_ptr_aligned(buffer.as_ptr()));
471
472 assert_eq!(&buffer[..initial_capacity], vec![7; initial_capacity]);
473 assert_eq!(buffer[initial_capacity], u32::MAX);
474 assert_eq!(state.allocations.load(Ordering::Relaxed), 1);
475 assert_eq!(state.deallocations.load(Ordering::Relaxed), 0);
476 assert_eq!(state.grows.load(Ordering::Relaxed), 1);
477
478 drop(buffer);
479 assert_eq!(state.deallocations.load(Ordering::Relaxed), 1);
480 }
481
482 #[test]
483 fn zero_capacity_does_not_allocate() {
484 let allocator = TrackingAllocator::default();
485 let state = Arc::clone(&allocator.state);
486 let mut buffer = BufferAllocatorRef::new(allocator).with_capacity::<u32>(0);
487
488 assert_eq!(buffer.capacity(), 0);
489 assert!(Alignment::DEFAULT_ALIGNMENT.is_offset_aligned(buffer.as_ptr().addr()));
490 assert_eq!(state.allocations.load(Ordering::Relaxed), 0);
491
492 buffer.push(42);
493
494 assert_eq!(buffer.as_slice(), [42]);
495 assert_eq!(state.allocations.load(Ordering::Relaxed), 1);
496 assert_eq!(state.grows.load(Ordering::Relaxed), 0);
497 }
498
499 #[test]
500 fn empty_buffers_preserve_allocator_without_allocating() -> VortexResult<()> {
501 let allocator = TrackingAllocator::default();
502 let state = Arc::clone(&allocator.state);
503 let allocator = BufferAllocatorRef::new(allocator);
504 let buffer = BufferMut::<u32>::zeroed_in(0, allocator.clone());
505 let buffer = buffer.freeze();
506 let copy = buffer.clone().into_mut();
507 assert!(copy.allocator().ptr_eq(&allocator));
508 let mut buffer = buffer
509 .try_into_mut()
510 .map_err(|_| vortex_err!("unique buffer"))?;
511 buffer.reserve(0);
512 assert!(buffer.is_empty());
513 assert!(buffer.allocator().ptr_eq(&allocator));
514 drop((copy, buffer));
515 assert_eq!(state.allocations.load(Ordering::Relaxed), 0);
516 assert_eq!(state.grows.load(Ordering::Relaxed), 0);
517 assert_eq!(state.deallocations.load(Ordering::Relaxed), 0);
518 Ok(())
519 }
520
521 #[test]
522 fn shared_into_mut_preserves_allocator() {
523 let allocator = TrackingAllocator::default();
524 let state = Arc::clone(&allocator.state);
525 let allocator = BufferAllocatorRef::new(allocator);
526 let original = allocator.copy_from([1u32, 2, 3]).freeze();
527 let mut copy = original.clone().into_mut();
528 assert!(copy.allocator().ptr_eq(&allocator));
529 copy[0] = 42;
530 assert_eq!(original.as_slice(), [1, 2, 3]);
531 assert_eq!(copy.as_slice(), [42, 2, 3]);
532 assert_eq!(state.allocations.load(Ordering::Relaxed), 2);
533 drop(copy);
534 assert_eq!(state.deallocations.load(Ordering::Relaxed), 1);
535 drop(original);
536 assert_eq!(state.deallocations.load(Ordering::Relaxed), 2);
537 }
538}