Skip to content

Commit e68a751

Browse files
committed
Merge branch 'main' into feature-spare-elements
2 parents d3a5a6f + eb3efca commit e68a751

File tree

2 files changed

+83
-57
lines changed

2 files changed

+83
-57
lines changed

src/lib.rs

Lines changed: 69 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use core::{
77
mem::{self, ManuallyDrop, MaybeUninit},
88
ops::{Deref, DerefMut, Index, IndexMut},
99
ptr,
10+
ptr::NonNull,
1011
slice::SliceIndex,
1112
};
1213

@@ -51,7 +52,7 @@ union AlignedHeader<H, T> {
5152
/// All of the data, like our header `OurHeaderType { a: 2 }`, the length of the vector: `2`,
5253
/// and the contents of the vector `['x', 'z']` resides on the other side of the pointer.
5354
pub struct HeaderVec<H, T> {
54-
ptr: *mut AlignedHeader<H, T>,
55+
ptr: NonNull<AlignedHeader<H, T>>,
5556
}
5657

5758
impl<H, T> HeaderVec<H, T> {
@@ -64,10 +65,10 @@ impl<H, T> HeaderVec<H, T> {
6465
let layout = Self::layout(capacity);
6566
let ptr = unsafe { alloc::alloc::alloc(layout) } as *mut AlignedHeader<H, T>;
6667

67-
// Handle out-of-memory.
68-
if ptr.is_null() {
68+
let Some(ptr) = NonNull::new(ptr) else {
69+
// Handle out-of-memory.
6970
alloc::alloc::handle_alloc_error(layout);
70-
}
71+
};
7172

7273
// Create self.
7374
let mut this = Self { ptr };
@@ -172,14 +173,14 @@ impl<H, T> HeaderVec<H, T> {
172173
/// This is useful to check if two nodes are the same. Use it with [`HeaderVec::is`].
173174
#[inline(always)]
174175
pub fn ptr(&self) -> *const () {
175-
self.ptr as *const ()
176+
self.ptr.as_ptr() as *const ()
176177
}
177178

178179
/// This is used to check if this is the `HeaderVec` that corresponds to the given pointer.
179180
/// This is useful for updating weak references after [`HeaderVec::push`] returns the pointer.
180181
#[inline(always)]
181182
pub fn is(&self, ptr: *const ()) -> bool {
182-
self.ptr as *const () == ptr
183+
self.ptr.as_ptr() as *const () == ptr
183184
}
184185

185186
/// Create a (dangerous) weak reference to the `HeaderVec`. This is useful to be able
@@ -254,8 +255,8 @@ impl<H, T> HeaderVec<H, T> {
254255
self.shrink_to(len)
255256
}
256257

257-
/// Resize the vector to have at least room for `additional` more elements.
258-
/// does exact resizing if `exact` is true.
258+
/// Resize the vector to least `requested_capacity` elements.
259+
/// Does exact resizing if `exact` is true.
259260
///
260261
/// Returns `Some(*const ())` if the memory was moved to a new location.
261262
///
@@ -264,7 +265,8 @@ impl<H, T> HeaderVec<H, T> {
264265
/// `requested_capacity` must be greater or equal than `self.len()`
265266
#[cold]
266267
unsafe fn resize_cold(&mut self, requested_capacity: usize, exact: bool) -> Option<*const ()> {
267-
// For efficiency we do only a debug_assert here
268+
// For efficiency we do only a debug_assert here, this is a internal unsafe function
269+
// it's contract should be already enforced by the caller which is under our control
268270
debug_assert!(
269271
self.len_exact() <= requested_capacity,
270272
"requested capacity is less than current length"
@@ -299,19 +301,21 @@ impl<H, T> HeaderVec<H, T> {
299301
// Reallocate the pointer.
300302
let ptr = unsafe {
301303
alloc::alloc::realloc(
302-
self.ptr as *mut u8,
304+
self.ptr.as_ptr() as *mut u8,
303305
Self::layout(old_capacity),
304306
Self::elems_to_mem_bytes(new_capacity),
305307
) as *mut AlignedHeader<H, T>
306308
};
307-
// Handle out-of-memory.
308-
if ptr.is_null() {
309+
310+
let Some(ptr) = NonNull::new(ptr) else {
311+
// Handle out-of-memory.
309312
alloc::alloc::handle_alloc_error(Self::layout(new_capacity));
310-
}
313+
};
314+
311315
// Check if the new pointer is different than the old one.
312316
let previous_pointer = if ptr != self.ptr {
313317
// Give the user the old pointer so they can update everything.
314-
Some(self.ptr as *const ())
318+
Some(self.ptr())
315319
} else {
316320
None
317321
};
@@ -413,7 +417,7 @@ impl<H, T> HeaderVec<H, T> {
413417
const fn offset() -> usize {
414418
// The first location, in units of size_of::<T>(), that is after the header
415419
// It's the end of the header, rounded up to the nearest size_of::<T>()
416-
mem::size_of::<AlignedHeader<H, T>>() / mem::size_of::<T>()
420+
(mem::size_of::<AlignedHeader<H, T>>() - 1) / mem::size_of::<T>() + 1
417421
}
418422

419423
/// Compute the number of elements (in units of T) to allocate for a given capacity.
@@ -441,13 +445,13 @@ impl<H, T> HeaderVec<H, T> {
441445
/// Gets the pointer to the start of the slice.
442446
#[inline(always)]
443447
fn start_ptr(&self) -> *const T {
444-
unsafe { (self.ptr as *const T).add(Self::offset()) }
448+
unsafe { (self.ptr.as_ptr() as *const T).add(Self::offset()) }
445449
}
446450

447451
/// Gets the pointer to the start of the slice.
448452
#[inline(always)]
449453
fn start_ptr_mut(&mut self) -> *mut T {
450-
unsafe { (self.ptr as *mut T).add(Self::offset()) }
454+
unsafe { (self.ptr.as_ptr() as *mut T).add(Self::offset()) }
451455
}
452456

453457
/// Gets the pointer to the end of the slice. This returns a mutable pointer to
@@ -460,13 +464,13 @@ impl<H, T> HeaderVec<H, T> {
460464
#[inline(always)]
461465
fn header(&self) -> &HeaderVecHeader<H> {
462466
// The beginning of the memory is always the header.
463-
unsafe { &*(self.ptr as *const HeaderVecHeader<H>) }
467+
unsafe { &*(self.ptr.as_ptr() as *const HeaderVecHeader<H>) }
464468
}
465469

466470
#[inline(always)]
467471
fn header_mut(&mut self) -> &mut HeaderVecHeader<H> {
468472
// The beginning of the memory is always the header.
469-
unsafe { &mut *(self.ptr as *mut HeaderVecHeader<H>) }
473+
unsafe { &mut *(self.ptr.as_ptr() as *mut HeaderVecHeader<H>) }
470474
}
471475
}
472476

@@ -494,8 +498,52 @@ impl<H, T: Clone> HeaderVec<H, T> {
494498

495499
#[cfg(feature = "atomic_append")]
496500
/// The atomic append API is only enabled when the `atomic_append` feature flag is set (which
497-
/// is the default).
501+
/// is the default). The [`push_atomic()`] or [`extend_from_slice_atomic()`] methods then
502+
/// become available and some internals using atomic operations.
503+
///
504+
/// This API implements interior-mutable appending to a shared `HeaderVec`. To other threads
505+
/// the appended elements are either not seen or all seen at once. Without additional
506+
/// synchronization these appends are racy but memory safe. The intention behind this API is to
507+
/// provide facilities for building other container abstractions the benefit from the shared
508+
/// non blocking nature while being unaffected from the racy semantics or provide synchronization
509+
/// on their own (Eg: reference counted data, interners, streaming parsers, etc). Since the
510+
/// `HeaderVec` is a shared object and we have only a `&self`, it can not be reallocated and moved,
511+
/// therefore appending can only be done within the reserved capacity.
512+
///
513+
/// # Safety
514+
///
515+
/// Only one single thread must try to [`push_atomic()`] or [`extend_from_slice_atomic()`] the
516+
/// `HeaderVec` at at time using the atomic append API's. The actual implementations of this
517+
/// restriction is left to the caller. This can be done by mutexes or guard objects. Or
518+
/// simply by staying single threaded or ensuring somehow else that there is only a single
519+
/// thread using the atomic_appending API.
498520
impl<H, T> HeaderVec<H, T> {
521+
/// Atomically adds an item to the end of the list without reallocation.
522+
///
523+
/// # Errors
524+
///
525+
/// If the vector is full, the item is returned.
526+
///
527+
/// # Safety
528+
///
529+
/// There must be only one thread calling this method at any time. Synchronization has to
530+
/// be provided by the user.
531+
pub unsafe fn push_atomic(&self, item: T) -> Result<(), T> {
532+
// relaxed is good enough here because this should be the only thread calling this method.
533+
let len = self.len_atomic_relaxed();
534+
if len < self.capacity() {
535+
unsafe {
536+
core::ptr::write(self.end_ptr_atomic_mut(), item);
537+
};
538+
let len_again = self.len_atomic_add_release(1);
539+
// in debug builds we check for races, the chance to catch these are still pretty minimal
540+
debug_assert_eq!(len_again, len, "len was updated by another thread");
541+
Ok(())
542+
} else {
543+
Err(item)
544+
}
545+
}
546+
499547
/// Get the length of the vector with `Ordering::Acquire`. This ensures that the length is
500548
/// properly synchronized after it got atomically updated.
501549
#[inline(always)]
@@ -521,48 +569,12 @@ impl<H, T> HeaderVec<H, T> {
521569
self.header().len.fetch_add(n, Ordering::Release)
522570
}
523571

524-
#[inline(always)]
525-
pub fn is_empty_atomic_acquire(&self) -> bool {
526-
self.len_atomic_acquire() == 0
527-
}
528-
529-
#[inline(always)]
530-
pub fn as_slice_atomic_acquire(&self) -> &[T] {
531-
unsafe { core::slice::from_raw_parts(self.start_ptr(), self.len_atomic_acquire()) }
532-
}
533-
534572
/// Gets the pointer to the end of the slice. This returns a mutable pointer to
535573
/// uninitialized memory behind the last element.
536574
#[inline(always)]
537575
fn end_ptr_atomic_mut(&self) -> *mut T {
538576
unsafe { self.start_ptr().add(self.len_atomic_acquire()) as *mut T }
539577
}
540-
541-
/// Atomically adds an item to the end of the list without reallocation.
542-
///
543-
/// # Errors
544-
///
545-
/// If the vector is full, the item is returned.
546-
///
547-
/// # Safety
548-
///
549-
/// There must be only one thread calling this method at any time. Synchronization has to
550-
/// be provided by the user.
551-
pub unsafe fn push_atomic(&self, item: T) -> Result<(), T> {
552-
// relaxed is good enough here because this should be the only thread calling this method.
553-
let len = self.len_atomic_relaxed();
554-
if len < self.capacity() {
555-
unsafe {
556-
core::ptr::write(self.end_ptr_atomic_mut(), item);
557-
};
558-
let len_again = self.len_atomic_add_release(1);
559-
// in debug builds we check for races, the chance to catch these are still pretty minimal
560-
debug_assert_eq!(len_again, len, "len was updated by another thread");
561-
Ok(())
562-
} else {
563-
Err(item)
564-
}
565-
}
566578
}
567579

568580
#[cfg(feature = "atomic_append")]
@@ -607,7 +619,7 @@ impl<H, T> Drop for HeaderVec<H, T> {
607619
for ix in 0..self.len_exact() {
608620
ptr::drop_in_place(self.start_ptr_mut().add(ix));
609621
}
610-
alloc::alloc::dealloc(self.ptr as *mut u8, Self::layout(self.capacity()));
622+
alloc::alloc::dealloc(self.ptr.as_ptr() as *mut u8, Self::layout(self.capacity()));
611623
}
612624
}
613625
}

tests/simple.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,20 @@ struct TestA {
1111
c: usize,
1212
}
1313

14+
#[test]
15+
fn test_sizeof() {
16+
// assert that HeaderVec is really a single lean pointer
17+
assert_eq!(
18+
core::mem::size_of::<HeaderVec<(), ()>>(),
19+
core::mem::size_of::<*mut ()>()
20+
);
21+
// and has space for niche optimization
22+
assert_eq!(
23+
core::mem::size_of::<HeaderVec<(), ()>>(),
24+
core::mem::size_of::<Option<HeaderVec<(), ()>>>()
25+
);
26+
}
27+
1428
#[test]
1529
fn test_empty() {
1630
let mut v_empty = HeaderVec::with_capacity(0, TestA { a: 4, b: !0, c: 66 });

0 commit comments

Comments
 (0)