use alloc::vec::Vec;
#[doc(hidden)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
#[error("host allocation failed for {requested_bytes} bytes")]
pub struct HostAllocationError {
requested_bytes: usize,
}
impl HostAllocationError {
#[doc(hidden)]
#[must_use]
pub const fn for_elements<T>(element_count: usize) -> Self {
allocation_error::<T>(element_count)
}
#[doc(hidden)]
#[must_use]
pub const fn requested_bytes(self) -> usize {
self.requested_bytes
}
}
#[doc(hidden)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
#[error(
"actual host allocation capacity requires {requested_bytes} bytes, exceeding the {cap_bytes}-byte phase cap"
)]
pub struct HostAllocationLimitError {
requested_bytes: usize,
cap_bytes: usize,
}
impl HostAllocationLimitError {
#[doc(hidden)]
#[must_use]
pub const fn requested_bytes(self) -> usize {
self.requested_bytes
}
#[doc(hidden)]
#[must_use]
pub const fn cap_bytes(self) -> usize {
self.cap_bytes
}
}
#[doc(hidden)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct HostAllocationBudget {
live_bytes: usize,
cap_bytes: usize,
}
impl HostAllocationBudget {
#[doc(hidden)]
#[must_use]
pub const fn new(cap_bytes: usize) -> Self {
Self {
live_bytes: 0,
cap_bytes,
}
}
#[doc(hidden)]
#[must_use]
pub const fn live_bytes(self) -> usize {
self.live_bytes
}
#[doc(hidden)]
#[must_use]
pub const fn cap_bytes(self) -> usize {
self.cap_bytes
}
#[doc(hidden)]
pub fn check_capacity<T>(&self, capacity: usize) -> Result<usize, HostAllocationLimitError> {
let owner_bytes = host_capacity_bytes::<T>(capacity);
self.checked_live_bytes(owner_bytes)?;
Ok(owner_bytes)
}
#[doc(hidden)]
pub fn account_capacity<T>(
&mut self,
capacity: usize,
) -> Result<usize, HostAllocationLimitError> {
let owner_bytes = host_capacity_bytes::<T>(capacity);
self.account_bytes(owner_bytes)?;
Ok(owner_bytes)
}
#[doc(hidden)]
pub fn account_bytes(&mut self, owner_bytes: usize) -> Result<(), HostAllocationLimitError> {
let requested_bytes = self.checked_live_bytes(owner_bytes)?;
self.live_bytes = requested_bytes;
Ok(())
}
#[doc(hidden)]
pub fn account_vec<T>(&mut self, values: &Vec<T>) -> Result<usize, HostAllocationLimitError> {
self.account_capacity::<T>(values.capacity())
}
fn checked_live_bytes(&self, owner_bytes: usize) -> Result<usize, HostAllocationLimitError> {
let requested_bytes = self.live_bytes.saturating_add(owner_bytes);
if requested_bytes > self.cap_bytes {
return Err(HostAllocationLimitError {
requested_bytes,
cap_bytes: self.cap_bytes,
});
}
Ok(requested_bytes)
}
}
#[doc(hidden)]
#[must_use]
pub const fn host_capacity_bytes<T>(capacity: usize) -> usize {
capacity.saturating_mul(core::mem::size_of::<T>())
}
#[doc(hidden)]
pub fn try_host_vec_with_capacity<T>(capacity: usize) -> Result<Vec<T>, HostAllocationError> {
let mut values = Vec::new();
values
.try_reserve_exact(capacity)
.map_err(|_| HostAllocationError::for_elements::<T>(capacity))?;
Ok(values)
}
#[doc(hidden)]
pub fn try_host_vec_filled<T: Clone>(len: usize, value: T) -> Result<Vec<T>, HostAllocationError> {
let mut values = try_host_vec_with_capacity(len)?;
values.resize(len, value);
Ok(values)
}
#[doc(hidden)]
pub fn try_host_vec_from_slice<T: Copy>(source: &[T]) -> Result<Vec<T>, HostAllocationError> {
let mut values = try_host_vec_with_capacity(source.len())?;
values.extend_from_slice(source);
Ok(values)
}
#[doc(hidden)]
pub fn try_host_vec_resize<T: Clone>(
values: &mut Vec<T>,
new_len: usize,
value: T,
) -> Result<(), HostAllocationError> {
if new_len > values.len() {
values
.try_reserve_exact(new_len - values.len())
.map_err(|_| HostAllocationError::for_elements::<T>(new_len))?;
}
values.resize(new_len, value);
Ok(())
}
const fn allocation_error<T>(element_count: usize) -> HostAllocationError {
HostAllocationError {
requested_bytes: match element_count.checked_mul(core::mem::size_of::<T>()) {
Some(bytes) => bytes,
None => usize::MAX,
},
}
}
#[cfg(test)]
mod tests {
use super::{
host_capacity_bytes, try_host_vec_filled, try_host_vec_from_slice, try_host_vec_resize,
try_host_vec_with_capacity, HostAllocationBudget, HostAllocationLimitError,
};
#[test]
fn impossible_capacity_reports_saturated_requested_bytes() {
let error = try_host_vec_with_capacity::<u32>(usize::MAX).unwrap_err();
assert_eq!(error.requested_bytes(), usize::MAX);
}
#[test]
fn initialized_copied_and_resized_vectors_preserve_contents() {
assert_eq!(try_host_vec_filled(3, 7u16).unwrap(), [7, 7, 7]);
assert_eq!(try_host_vec_from_slice(&[1u8, 2, 3]).unwrap(), [1, 2, 3]);
let mut values = try_host_vec_from_slice(&[4u8]).unwrap();
try_host_vec_resize(&mut values, 3, 9).unwrap();
assert_eq!(values, [4, 9, 9]);
try_host_vec_resize(&mut values, 1, 0).unwrap();
assert_eq!(values, [4]);
}
#[test]
fn actual_capacity_budget_accepts_exact_cap_and_rejects_one_over() {
let mut exact = HostAllocationBudget::new(16);
assert_eq!(exact.account_capacity::<u32>(4), Ok(16));
assert_eq!(exact.live_bytes(), 16);
let mut one_over = HostAllocationBudget::new(15);
assert_eq!(
one_over.account_capacity::<u32>(4),
Err(HostAllocationLimitError {
requested_bytes: 16,
cap_bytes: 15,
})
);
assert_eq!(one_over.live_bytes(), 0);
}
#[test]
fn logical_capacity_preflight_does_not_mutate_the_budget() {
let budget = HostAllocationBudget::new(16);
assert_eq!(budget.check_capacity::<u32>(4), Ok(16));
assert_eq!(budget.live_bytes(), 0);
assert_eq!(
budget.check_capacity::<u32>(5),
Err(HostAllocationLimitError {
requested_bytes: 20,
cap_bytes: 16,
})
);
assert_eq!(budget.live_bytes(), 0);
}
#[test]
fn allocator_overcapacity_is_accounted_instead_of_logical_length() {
let mut values = try_host_vec_with_capacity::<u8>(17).unwrap();
values.extend_from_slice(&[0; 8]);
let actual_bytes = host_capacity_bytes::<u8>(values.capacity());
assert!(actual_bytes >= 17);
let mut budget = HostAllocationBudget::new(16);
assert_eq!(
budget.account_vec(&values),
Err(HostAllocationLimitError {
requested_bytes: actual_bytes,
cap_bytes: 16,
})
);
}
#[test]
fn existing_vector_growth_is_reconciled_from_current_capacity() {
let mut values = try_host_vec_with_capacity::<u8>(8).unwrap();
values.try_reserve_exact(9).unwrap();
let actual_bytes = host_capacity_bytes::<u8>(values.capacity());
let mut exact = HostAllocationBudget::new(actual_bytes);
assert_eq!(exact.account_vec(&values), Ok(actual_bytes));
let mut one_under = HostAllocationBudget::new(actual_bytes.saturating_sub(1));
assert!(matches!(
one_under.account_vec(&values),
Err(HostAllocationLimitError {
requested_bytes,
cap_bytes,
}) if requested_bytes == actual_bytes && cap_bytes == actual_bytes.saturating_sub(1)
));
}
#[test]
fn zero_sized_capacity_uses_zero_budget_bytes() {
assert_eq!(host_capacity_bytes::<()>(usize::MAX), 0);
let mut budget = HostAllocationBudget::new(0);
assert_eq!(budget.account_capacity::<()>(usize::MAX), Ok(0));
}
}