Skip to main content

qualia_core_db/solvers/calculus/
workspace.rs

1//! Caller-owned calculus workspace accounting.
2
3pub const SENTINEL_PASS_BYTES: usize = 42 * 1024 * 1024;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum WorkspaceError {
7    SizeOverflow,
8    SentinelExceeded { required: usize },
9    BufferTooSmall { required: usize, available: usize },
10}
11
12pub const fn checked_workspace_bytes<T>(elements: usize) -> Result<usize, WorkspaceError> {
13    let Some(required) = elements.checked_mul(core::mem::size_of::<T>()) else {
14        return Err(WorkspaceError::SizeOverflow);
15    };
16    if required > SENTINEL_PASS_BYTES {
17        return Err(WorkspaceError::SentinelExceeded { required });
18    }
19    Ok(required)
20}
21
22pub struct Workspace<'a, T> {
23    storage: &'a mut [T],
24    used: usize,
25}
26
27impl<'a, T> Workspace<'a, T> {
28    pub fn new(storage: &'a mut [T]) -> Result<Self, WorkspaceError> {
29        checked_workspace_bytes::<T>(storage.len())?;
30        Ok(Self { storage, used: 0 })
31    }
32
33    pub const fn capacity(&self) -> usize {
34        self.storage.len()
35    }
36
37    pub const fn used(&self) -> usize {
38        self.used
39    }
40
41    pub const fn remaining(&self) -> usize {
42        self.storage.len() - self.used
43    }
44
45    pub fn reset(&mut self) {
46        self.used = 0;
47    }
48
49    pub fn take(&mut self, elements: usize) -> Result<&mut [T], WorkspaceError> {
50        let end = self
51            .used
52            .checked_add(elements)
53            .ok_or(WorkspaceError::SizeOverflow)?;
54        if end > self.storage.len() {
55            return Err(WorkspaceError::BufferTooSmall {
56                required: end,
57                available: self.storage.len(),
58            });
59        }
60        let start = self.used;
61        self.used = end;
62        Ok(&mut self.storage[start..end])
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn workspace_is_caller_owned_and_resettable() {
72        let mut storage = [0.0_f64; 16];
73        let mut workspace = Workspace::new(&mut storage).unwrap();
74        assert_eq!(workspace.take(5).unwrap().len(), 5);
75        assert_eq!(workspace.used(), 5);
76        assert_eq!(workspace.remaining(), 11);
77        workspace.reset();
78        assert_eq!(workspace.used(), 0);
79    }
80
81    #[test]
82    fn sentinel_and_capacity_fail_closed() {
83        assert!(matches!(
84            checked_workspace_bytes::<u64>(SENTINEL_PASS_BYTES / 8 + 1),
85            Err(WorkspaceError::SentinelExceeded { .. })
86        ));
87        let mut storage = [0_u32; 4];
88        let mut workspace = Workspace::new(&mut storage).unwrap();
89        assert_eq!(
90            workspace.take(5),
91            Err(WorkspaceError::BufferTooSmall {
92                required: 5,
93                available: 4
94            })
95        );
96    }
97}