use super::span::to_index;
use super::{Scratch, ScratchExhausted, Span};
impl Scratch<'_> {
pub(super) fn write_result<const N: usize>(
&mut self,
operands: [Span; N],
needed: u64,
write: impl FnOnce([&[f64]; N], &mut [f64]) -> usize,
) -> Result<Span, ScratchExhausted> {
for operand in operands {
self.debug_check(operand);
}
let start = self.used;
let end = self.reserve(needed)?;
let (below, reserved) =
self.storage.as_mut_slice()[..to_index(end)].split_at_mut(to_index(start));
let len = write(operands.map(|operand| &below[operand.range()]), reserved);
assert!(len <= reserved.len(), "a result exceeded its reservation");
let len = u32::try_from(len).expect("a result fits in its reservation, below u32::MAX");
self.used = start + len;
Ok(Span { start, len })
}
fn reserve(&mut self, needed: u64) -> Result<u32, ScratchExhausted> {
let end = u64::from(self.used)
.checked_add(needed)
.ok_or(ScratchExhausted)?;
let end = u32::try_from(end).map_err(|_| ScratchExhausted)?;
self.storage.make_available(end)?;
self.high_water = self.high_water.max(end);
Ok(end)
}
pub(super) fn debug_check(&self, s: Span) {
debug_assert!(
u64::from(s.start) + u64::from(s.len) <= u64::from(self.used),
"{s:?} reaches past the top at {}: it was released by a reset or \
belongs to another arena",
self.used
);
}
}
#[cfg(test)]
mod tests {
use super::{Scratch, ScratchExhausted};
#[test]
fn reservations_past_u64_max_fail_without_changes() {
let mut buf = [0.0; 4];
let mut s = Scratch::borrowed(&mut buf);
let _ = s.push(&[1.0]).unwrap();
assert_eq!(s.reserve(u64::MAX), Err(ScratchExhausted));
assert_eq!(s.reserve(u64::from(u32::MAX)), Err(ScratchExhausted));
assert_eq!((s.used(), s.high_water()), (1, 1));
assert_eq!(s.reserve(3), Ok(4));
}
#[cfg(feature = "alloc")]
#[test]
fn growable_reservations_past_u32_max_fail_before_allocating() {
let mut s = Scratch::growable(2);
let _ = s.push(&[1.0]).unwrap();
assert_eq!(s.reserve(u64::from(u32::MAX)), Err(ScratchExhausted));
assert_eq!(s.reserve(u64::MAX), Err(ScratchExhausted));
assert_eq!((s.used(), s.capacity(), s.high_water()), (1, 2, 1));
}
}