use std::alloc::Layout;
use std::mem::{ManuallyDrop, MaybeUninit};
use strided_basic::StridedViewMut;
use tenferro_tensor::{
validate::checked_shape_product, IntoShapeVec, ShapeVec, TensorRank, TypedTensor,
};
use crate::buffer_pool::{BufferPool, PoolScalar, UninitCheckoutToken};
use crate::{Error, Result};
fn checked_compact_strides(shape: &[usize]) -> Result<tenferro_tensor::StrideVec> {
let mut strides = tenferro_tensor::StrideVec::with_capacity(shape.len());
let mut stride = 1isize;
for &dim in shape {
strides.push(stride);
let dim = isize::try_from(dim).map_err(|_| {
Error::invalid_argument("pooled_uninit_output", "shape", "dimension exceeds isize")
})?;
stride = stride.checked_mul(dim).ok_or_else(|| {
Error::invalid_argument("pooled_uninit_output", "shape", "compact stride overflow")
})?;
}
Ok(strides)
}
#[derive(Debug)]
pub struct PooledUninitOutput<T: PoolScalar> {
pool: BufferPool,
shape: ShapeVec,
strides: tenferro_tensor::StrideVec,
data: Vec<std::mem::MaybeUninit<T>>,
checkout: Option<UninitCheckoutToken>,
byte_len: usize,
}
impl<T: PoolScalar> PooledUninitOutput<T> {
pub fn new(pool: &BufferPool, shape: impl IntoShapeVec) -> Result<Self> {
let shape = shape.into_shape_vec();
let len = checked_shape_product("pooled_uninit_output", "shape", &shape)?;
let layout = Layout::array::<T>(len).map_err(|_| {
Error::invalid_argument(
"pooled_uninit_output",
"shape",
"element byte layout exceeds allocation limits",
)
})?;
let byte_len = layout.size();
let strides = checked_compact_strides(&shape)?;
let mut pool = pool.checkout_handle();
let (data, checkout) =
<T as crate::buffer_pool::private::Sealed>::pool_acquire_uninit_tracked(
&mut pool, len,
)?;
debug_assert!(match checkout {
UninitCheckoutToken::Fresh { actual_capacity }
| UninitCheckoutToken::Reused { actual_capacity } => {
data.capacity() == actual_capacity
}
});
Ok(Self {
pool,
shape,
strides,
data,
checkout: Some(checkout),
byte_len,
})
}
pub fn as_uninit_slice_mut(&mut self) -> &mut [MaybeUninit<T>] {
self.data.as_mut_slice()
}
pub fn as_uninit_view_mut(&mut self) -> Result<StridedViewMut<'_, MaybeUninit<T>>> {
let shape = &self.shape;
let strides = &self.strides;
let data = &mut self.data;
StridedViewMut::new(data, shape, strides, 0)
.map_err(|err| Error::backend_source("pooled_uninit_output", err))
}
#[cfg(test)]
pub(crate) fn token_is_reused_with_capacity(&self, capacity: usize) -> bool {
matches!(self.checkout, Some(UninitCheckoutToken::Reused { actual_capacity }) if actual_capacity == capacity)
}
#[cfg(test)]
pub(crate) fn token_is_fresh(&self) -> bool {
matches!(
self.checkout,
Some(UninitCheckoutToken::Fresh { actual_capacity })
if self.data.capacity() == actual_capacity
)
}
pub fn as_uninit_bytes_mut(&mut self) -> &mut [std::mem::MaybeUninit<u8>] {
let data = self.as_uninit_slice_mut();
unsafe {
std::slice::from_raw_parts_mut(
data.as_mut_ptr().cast::<std::mem::MaybeUninit<u8>>(),
self.byte_len,
)
}
}
pub unsafe fn assume_init_as_recycled<R: TensorRank>(self) -> Result<TypedTensor<T, R>> {
unsafe { self.finish() }
}
pub unsafe fn assume_init_recycled(self) -> Result<TypedTensor<T>> {
unsafe { self.finish() }
}
unsafe fn finish<R: TensorRank>(mut self) -> Result<TypedTensor<T, R>> {
let shape = R::shape_from_vec(std::mem::take(&mut self.shape))
.map_err(|err| Error::backend_source("pooled_uninit_output", err))?;
let data = std::mem::take(&mut self.data);
let mut data = ManuallyDrop::new(data);
let data = unsafe {
Vec::from_raw_parts(data.as_mut_ptr().cast::<T>(), data.len(), data.capacity())
};
let Some(checkout) = self.checkout.take() else {
unreachable!("a live pooled output owns its checkout");
};
let recycler = <T as crate::buffer_pool::private::Sealed>::pool_finish_recycled(
&mut self.pool,
checkout,
);
TypedTensor::from_vec_col_major_with_recycler(shape, data, recycler)
}
}
impl<T: PoolScalar> Drop for PooledUninitOutput<T> {
fn drop(&mut self) {
if self.checkout.is_none() {
return;
}
let data = std::mem::take(&mut self.data);
let mut data = ManuallyDrop::new(data);
let buf = unsafe {
Vec::from_raw_parts(data.as_mut_ptr().cast::<T>(), data.len(), data.capacity())
};
<T as PoolScalar>::pool_release(&mut self.pool, buf);
}
}
#[cfg(test)]
mod tests;