use crate::error::{Result, ZiporaError};
use crate::memory::simd_ops::{fast_copy, fast_fill};
use crate::simd::{AdaptiveSimdSelector, Operation};
use std::alloc::{self, Layout};
use std::mem;
use std::ptr::{self, NonNull};
use std::slice;
use std::time::Instant;
#[inline]
fn check_alignment<T>(ptr: *mut u8) {
debug_assert!(!ptr.is_null());
debug_assert!((ptr as usize).is_multiple_of(mem::align_of::<T>()));
}
#[inline]
fn cast_aligned_ptr<T>(ptr: *mut u8) -> *mut T {
check_alignment::<T>(ptr);
ptr as *mut T
}
#[inline]
const fn is_simd_safe<T>() -> bool {
!mem::needs_drop::<T>()
}
#[inline]
const fn is_simd_beneficial<T>(element_count: usize) -> bool {
const SIMD_THRESHOLD: usize = 64;
element_count * mem::size_of::<T>() >= SIMD_THRESHOLD
}
const PREFETCH_DISTANCE: usize = 8;
struct PrefetchOps;
impl PrefetchOps {
#[inline]
fn prefetch_read<T>(ptr: *const T) {
#[cfg(target_arch = "x86_64")]
unsafe {
std::arch::x86_64::_mm_prefetch(ptr as *const i8, std::arch::x86_64::_MM_HINT_T0);
}
#[cfg(target_arch = "aarch64")]
unsafe {
std::arch::asm!(
"prfm pldl1keep, [{0}]",
in(reg) ptr,
options(nostack, preserves_flags, readonly)
);
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
{
std::hint::black_box(ptr);
}
}
#[inline]
fn prefetch_range<T>(start: *const T, count: usize, distance: usize) {
if count <= distance {
return;
}
const CACHE_LINE_SIZE: usize = 64;
let element_size = mem::size_of::<T>();
let elements_per_line = (CACHE_LINE_SIZE / element_size).max(1);
for i in (0..count).step_by(elements_per_line) {
if i + distance < count {
unsafe {
Self::prefetch_read(start.add(i + distance));
}
}
}
}
}
#[inline]
unsafe fn slice_as_bytes<T>(slice: &[T]) -> &[u8] {
if slice.is_empty() {
&[]
} else {
unsafe { slice::from_raw_parts(slice.as_ptr() as *const u8, std::mem::size_of_val(slice)) }
}
}
#[inline]
unsafe fn slice_as_bytes_mut<T>(slice: &mut [T]) -> &mut [u8] {
if slice.is_empty() {
&mut []
} else {
unsafe {
slice::from_raw_parts_mut(slice.as_mut_ptr() as *mut u8, std::mem::size_of_val(slice))
}
}
}
pub struct FastVec<T> {
ptr: Option<NonNull<T>>,
len: usize,
cap: usize,
}
impl<T> FastVec<T> {
#[inline]
pub fn new() -> Self {
Self {
ptr: None,
len: 0,
cap: 0,
}
}
pub fn with_capacity(cap: usize) -> Result<Self> {
if mem::size_of::<T>() == 0 {
return Ok(Self {
ptr: Some(NonNull::dangling()),
len: 0,
cap: usize::MAX,
});
}
if cap == 0 {
return Ok(Self::new());
}
if cap > (isize::MAX as usize) / mem::size_of::<T>().max(1) {
return Err(ZiporaError::out_of_memory(cap * mem::size_of::<T>()));
}
let layout = Layout::array::<T>(cap)
.map_err(|_| ZiporaError::out_of_memory(cap * mem::size_of::<T>()))?;
let ptr = unsafe {
let raw_ptr = alloc::alloc(layout);
if raw_ptr.is_null() {
return Err(ZiporaError::out_of_memory(layout.size()));
}
cast_aligned_ptr::<T>(raw_ptr)
};
Ok(Self {
ptr: Some(unsafe { NonNull::new_unchecked(ptr) }),
len: 0,
cap,
})
}
pub fn with_capacity_zeroed(cap: usize) -> Result<Self>
where
T: bytemuck::Zeroable,
{
if mem::size_of::<T>() == 0 {
return Ok(Self {
ptr: Some(NonNull::dangling()),
len: cap,
cap: usize::MAX,
});
}
if cap == 0 {
return Ok(Self::new());
}
if cap > (isize::MAX as usize) / mem::size_of::<T>().max(1) {
return Err(ZiporaError::out_of_memory(cap * mem::size_of::<T>()));
}
let layout = Layout::array::<T>(cap)
.map_err(|_| ZiporaError::out_of_memory(cap * mem::size_of::<T>()))?;
let ptr = unsafe {
let raw_ptr = alloc::alloc_zeroed(layout);
if raw_ptr.is_null() {
return Err(ZiporaError::out_of_memory(layout.size()));
}
cast_aligned_ptr::<T>(raw_ptr)
};
Ok(Self {
ptr: Some(unsafe { NonNull::new_unchecked(ptr) }),
len: cap, cap,
})
}
pub fn from_vec(vec: Vec<T>) -> Self {
let mut vec = std::mem::ManuallyDrop::new(vec);
let ptr = vec.as_mut_ptr();
let len = vec.len();
let cap = vec.capacity();
Self {
ptr: NonNull::new(ptr),
len,
cap,
}
}
pub fn with_size(size: usize, value: T) -> Result<Self>
where
T: Clone,
{
let mut vec = Self::with_capacity(size)?;
vec.resize(size, value)?;
Ok(vec)
}
#[inline]
pub fn len(&self) -> usize {
self.len
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[inline]
pub fn capacity(&self) -> usize {
self.cap
}
#[inline]
pub fn as_ptr(&self) -> *const T {
match self.ptr {
Some(ptr) => ptr.as_ptr(),
None => ptr::null(),
}
}
#[inline]
pub fn as_mut_ptr(&mut self) -> *mut T {
match self.ptr {
Some(ptr) => ptr.as_ptr(),
None => ptr::null_mut(),
}
}
#[inline]
pub unsafe fn set_len(&mut self, new_len: usize) {
debug_assert!(new_len <= self.cap);
self.len = new_len;
}
#[inline]
pub fn as_slice(&self) -> &[T] {
unsafe { slice::from_raw_parts(self.ptr.unwrap_or(NonNull::dangling()).as_ptr(), self.len) }
}
#[inline(always)]
pub fn as_mut_slice(&mut self) -> &mut [T] {
unsafe {
slice::from_raw_parts_mut(self.ptr.unwrap_or(NonNull::dangling()).as_ptr(), self.len)
}
}
pub fn reserve(&mut self, additional: usize) -> Result<()> {
if additional > (isize::MAX as usize) / mem::size_of::<T>().max(1) {
return Err(ZiporaError::out_of_memory(additional * mem::size_of::<T>()));
}
let required = self
.len
.checked_add(additional)
.ok_or_else(|| ZiporaError::out_of_memory(usize::MAX))?;
if required <= self.cap {
return Ok(());
}
self.realloc(required)
}
pub fn ensure_capacity(&mut self, min_cap: usize) -> Result<()> {
if min_cap > (isize::MAX as usize) / mem::size_of::<T>().max(1) {
return Err(ZiporaError::out_of_memory(min_cap * mem::size_of::<T>()));
}
debug_assert!(min_cap >= self.len);
if min_cap <= self.cap {
return Ok(());
}
self.realloc(min_cap)
}
fn realloc(&mut self, new_cap: usize) -> Result<()> {
debug_assert!(new_cap >= self.len);
if mem::size_of::<T>() == 0 {
self.ptr = Some(NonNull::dangling());
self.cap = usize::MAX;
return Ok(());
}
if new_cap > (isize::MAX as usize) / mem::size_of::<T>().max(1) {
return Err(ZiporaError::out_of_memory(new_cap * mem::size_of::<T>()));
}
let target_cap = new_cap.max(self.cap.saturating_mul(2));
let new_layout = Layout::array::<T>(target_cap)
.map_err(|_| ZiporaError::out_of_memory(target_cap * mem::size_of::<T>()))?;
let new_ptr = match self.ptr {
Some(ptr) => {
if self.cap == 0 {
unsafe {
let raw_ptr = alloc::alloc(new_layout);
if raw_ptr.is_null() {
std::ptr::null_mut()
} else {
cast_aligned_ptr::<T>(raw_ptr)
}
}
} else {
let old_layout = Layout::array::<T>(self.cap)
.map_err(|_| ZiporaError::out_of_memory(self.cap * mem::size_of::<T>()))?;
unsafe {
let raw_ptr =
alloc::realloc(ptr.as_ptr() as *mut u8, old_layout, new_layout.size());
if raw_ptr.is_null() {
std::ptr::null_mut()
} else {
cast_aligned_ptr::<T>(raw_ptr)
}
}
}
}
None => unsafe {
let raw_ptr = alloc::alloc(new_layout);
if raw_ptr.is_null() {
std::ptr::null_mut()
} else {
cast_aligned_ptr::<T>(raw_ptr)
}
},
};
if new_ptr.is_null() {
return Err(ZiporaError::out_of_memory(new_layout.size()));
}
self.ptr = Some(unsafe { NonNull::new_unchecked(new_ptr) });
self.cap = target_cap;
Ok(())
}
pub fn push(&mut self, value: T) -> Result<()> {
debug_assert!(self.len <= self.cap);
if self.len >= (isize::MAX as usize) {
return Err(ZiporaError::invalid_state(
"vector length would exceed maximum",
));
}
if self.len >= self.cap {
self.ensure_capacity(self.len + 1)?;
}
debug_assert!(self.len < self.cap);
debug_assert!(self.ptr.is_some() || self.len == 0);
unsafe {
ptr::write(self.as_mut_ptr().add(self.len), value);
}
self.len += 1;
Ok(())
}
pub fn pop(&mut self) -> Option<T> {
debug_assert!(self.len <= self.cap);
if self.len == 0 {
None
} else {
debug_assert!(self.ptr.is_some());
self.len -= 1;
Some(unsafe { ptr::read(self.as_ptr().add(self.len)) })
}
}
pub fn insert(&mut self, index: usize, value: T) -> Result<()> {
if index > self.len {
return Err(ZiporaError::out_of_bounds(index, self.len));
}
debug_assert!(index <= self.len);
if self.len >= self.cap {
self.ensure_capacity(self.len + 1)?;
}
let move_count = self.len - index;
unsafe {
let ptr = self.as_mut_ptr().add(index);
if move_count > 0 {
ptr::copy(ptr, ptr.add(1), move_count);
}
ptr::write(ptr, value);
}
self.len += 1;
Ok(())
}
pub fn remove(&mut self, index: usize) -> Result<T> {
if index >= self.len {
return Err(ZiporaError::out_of_bounds(index, self.len));
}
let move_count = self.len - index - 1;
unsafe {
let ptr = self.as_mut_ptr().add(index);
let value = ptr::read(ptr);
if move_count > 0 {
ptr::copy(ptr.add(1), ptr, move_count);
}
self.len -= 1;
Ok(value)
}
}
pub fn resize(&mut self, new_len: usize, value: T) -> Result<()>
where
T: Clone,
{
if new_len > (isize::MAX as usize) / mem::size_of::<T>().max(1) {
return Err(ZiporaError::out_of_memory(new_len * mem::size_of::<T>()));
}
debug_assert!(self.len <= self.cap);
if new_len > self.len {
self.ensure_capacity(new_len)?;
debug_assert!(self.cap >= new_len);
debug_assert!(self.ptr.is_some());
let fill_count = new_len - self.len;
if is_simd_safe::<T>()
&& is_simd_beneficial::<T>(fill_count)
&& mem::size_of::<T>() == 1
{
unsafe {
let fill_slice = slice::from_raw_parts_mut(
self.as_mut_ptr().add(self.len) as *mut u8,
fill_count,
);
fast_fill(fill_slice, *((&value) as *const T as *const u8));
}
} else {
for i in self.len..new_len {
unsafe {
ptr::write(self.as_mut_ptr().add(i), value.clone());
}
self.len = i + 1;
}
}
} else if new_len < self.len {
debug_assert!(self.ptr.is_some() || self.len == 0);
for i in new_len..self.len {
unsafe {
ptr::drop_in_place(self.as_mut_ptr().add(i));
}
}
}
self.len = new_len;
debug_assert!(self.len <= self.cap);
Ok(())
}
pub fn resize_with<F>(&mut self, new_len: usize, f: F) -> Result<()>
where
F: FnMut() -> T,
{
if new_len > (isize::MAX as usize) / mem::size_of::<T>().max(1) {
return Err(ZiporaError::out_of_memory(new_len * mem::size_of::<T>()));
}
debug_assert!(self.len <= self.cap);
if new_len > self.len {
self.ensure_capacity(new_len)?;
debug_assert!(self.cap >= new_len);
debug_assert!(self.ptr.is_some());
let mut closure = f;
for i in self.len..new_len {
unsafe {
ptr::write(self.as_mut_ptr().add(i), closure());
}
self.len = i + 1;
}
} else if new_len < self.len {
for i in new_len..self.len {
unsafe {
ptr::drop_in_place(self.as_mut_ptr().add(i));
}
}
}
self.len = new_len;
debug_assert!(self.len <= self.cap);
Ok(())
}
pub fn clear(&mut self) {
debug_assert!(self.len <= self.cap);
debug_assert!(self.ptr.is_some() || self.len == 0);
for i in 0..self.len {
unsafe {
ptr::drop_in_place(self.as_mut_ptr().add(i));
}
}
self.len = 0;
debug_assert!(self.len <= self.cap);
}
pub fn shrink_to_fit(&mut self) -> Result<()> {
if mem::size_of::<T>() == 0 {
return Ok(());
}
if self.len == self.cap {
return Ok(());
}
if self.len == 0 {
if let Some(ptr) = self.ptr {
unsafe {
let layout = Layout::array::<T>(self.cap)
.map_err(|_| ZiporaError::out_of_memory(self.cap * mem::size_of::<T>()))?;
alloc::dealloc(ptr.as_ptr() as *mut u8, layout);
}
}
self.ptr = None;
self.cap = 0;
return Ok(());
}
let new_layout = Layout::array::<T>(self.len)
.map_err(|_| ZiporaError::out_of_memory(self.len * mem::size_of::<T>()))?;
let new_ptr = if let Some(ptr) = self.ptr {
let old_layout = Layout::array::<T>(self.cap)
.map_err(|_| ZiporaError::out_of_memory(self.cap * mem::size_of::<T>()))?;
unsafe {
let raw_ptr =
alloc::realloc(ptr.as_ptr() as *mut u8, old_layout, new_layout.size());
if raw_ptr.is_null() {
std::ptr::null_mut()
} else {
cast_aligned_ptr::<T>(raw_ptr)
}
}
} else {
return Ok(()); };
if new_ptr.is_null() {
return Err(ZiporaError::out_of_memory(new_layout.size()));
}
self.ptr = Some(unsafe { NonNull::new_unchecked(new_ptr) });
self.cap = self.len;
Ok(())
}
#[inline(always)]
pub unsafe fn get_unchecked(&self, index: usize) -> &T {
debug_assert!(index < self.len);
unsafe { &*self.ptr.unwrap_unchecked().as_ptr().add(index) }
}
#[inline(always)]
pub unsafe fn get_unchecked_mut(&mut self, index: usize) -> &mut T {
debug_assert!(index < self.len);
unsafe { &mut *self.ptr.unwrap_unchecked().as_ptr().add(index) }
}
pub fn extend<I>(&mut self, iter: I) -> Result<()>
where
I: IntoIterator<Item = T>,
I::IntoIter: ExactSizeIterator,
{
let iter = iter.into_iter();
let additional = iter.len();
self.reserve(additional)?;
for item in iter {
unsafe {
ptr::write(self.as_mut_ptr().add(self.len), item);
}
self.len += 1;
}
Ok(())
}
pub fn fill_range_fast(&mut self, start: usize, end: usize, value: T) -> Result<()>
where
T: Copy,
{
if start > end || end > self.len {
return Err(ZiporaError::out_of_bounds(end, self.len));
}
debug_assert!(start <= end);
debug_assert!(end <= self.len);
debug_assert!(self.len <= self.cap);
debug_assert!(self.ptr.is_some() || self.len == 0);
if start == end {
return Ok(()); }
let range_len = end - start;
if is_simd_safe::<T>() && is_simd_beneficial::<T>(range_len) {
let selector = AdaptiveSimdSelector::global();
let _ = selector.select_optimal_impl(
Operation::MemZero,
range_len * mem::size_of::<T>(),
None, );
let start_time = Instant::now();
if mem::size_of::<T>() == 1 {
unsafe {
let range_slice = slice::from_raw_parts_mut(
self.as_mut_ptr().add(start) as *mut u8,
range_len,
);
if range_len >= PREFETCH_DISTANCE * 8 {
PrefetchOps::prefetch_range(
range_slice.as_ptr(),
range_len,
PREFETCH_DISTANCE,
);
}
fast_fill(range_slice, *((&value) as *const T as *const u8));
}
} else {
let range_slice =
unsafe { slice::from_raw_parts_mut(self.as_mut_ptr().add(start), range_len) };
if range_len >= PREFETCH_DISTANCE * 2 {
for i in 0..range_len {
if i + PREFETCH_DISTANCE < range_len {
PrefetchOps::prefetch_read(
&range_slice[i + PREFETCH_DISTANCE] as *const T as *const u8
as *const i8,
);
}
range_slice[i] = value;
}
} else {
for item in range_slice.iter_mut() {
*item = value;
}
}
}
selector.monitor_performance(
Operation::MemZero,
start_time.elapsed(),
range_len as u64,
);
} else {
let range_slice = &mut self.as_mut_slice()[start..end];
for item in range_slice.iter_mut() {
*item = value;
}
}
Ok(())
}
pub fn copy_from_slice_fast(&mut self, src: &[T]) -> Result<()>
where
T: Copy,
{
if src.len() > (isize::MAX as usize) / mem::size_of::<T>().max(1) {
return Err(ZiporaError::out_of_memory(std::mem::size_of_val(src)));
}
debug_assert!(self.len <= self.cap);
if src.is_empty() {
return Ok(());
}
self.ensure_capacity(src.len())?;
debug_assert!(self.cap >= src.len());
debug_assert!(self.ptr.is_some());
if is_simd_safe::<T>() && is_simd_beneficial::<T>(src.len()) {
let selector = AdaptiveSimdSelector::global();
let _ = selector.select_optimal_impl(
Operation::Copy,
std::mem::size_of_val(src),
None, );
let start_time = Instant::now();
unsafe {
if src.len() >= PREFETCH_DISTANCE * 8 {
PrefetchOps::prefetch_range(src.as_ptr(), src.len(), PREFETCH_DISTANCE);
}
let src_bytes = slice_as_bytes(src);
let dst_bytes =
slice_as_bytes_mut(slice::from_raw_parts_mut(self.as_mut_ptr(), src.len()));
fast_copy(src_bytes, dst_bytes)?;
}
selector.monitor_performance(Operation::Copy, start_time.elapsed(), src.len() as u64);
} else {
unsafe {
ptr::copy_nonoverlapping(src.as_ptr(), self.as_mut_ptr(), src.len());
}
}
self.len = src.len();
Ok(())
}
pub fn extend_from_slice_fast(&mut self, src: &[T]) -> Result<()>
where
T: Copy,
{
if src.is_empty() {
return Ok(());
}
let old_len = self.len;
self.reserve(src.len())?;
if is_simd_safe::<T>() && is_simd_beneficial::<T>(src.len()) {
let selector = AdaptiveSimdSelector::global();
let _ = selector.select_optimal_impl(
Operation::Copy,
std::mem::size_of_val(src),
None, );
let start_time = Instant::now();
unsafe {
if src.len() >= PREFETCH_DISTANCE * 8 {
PrefetchOps::prefetch_range(src.as_ptr(), src.len(), PREFETCH_DISTANCE);
}
let src_bytes = slice_as_bytes(src);
let dst_bytes = slice_as_bytes_mut(slice::from_raw_parts_mut(
self.as_mut_ptr().add(old_len),
src.len(),
));
fast_copy(src_bytes, dst_bytes)?;
}
selector.monitor_performance(Operation::Copy, start_time.elapsed(), src.len() as u64);
} else {
unsafe {
ptr::copy_nonoverlapping(src.as_ptr(), self.as_mut_ptr().add(old_len), src.len());
}
}
self.len += src.len();
Ok(())
}
}
mod traits;
#[cfg(test)]
mod tests;