Skip to main content

sim_lib_machine/
slots.rs

1use std::marker::PhantomData;
2
3use sim_lib_control::AdmissionLimit;
4
5use crate::ValueWidthPolicy;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8struct Span {
9    start: usize,
10    width: usize,
11}
12
13/// Exact failure evidence from indexed slot storage.
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub enum SlotError {
16    /// The requested value would extend beyond the slot-file limit.
17    Overflow {
18        /// First slot requested by the operation.
19        slot: usize,
20        /// Logical width required by the value.
21        width: usize,
22        /// Total number of slots admitted for the file.
23        limit: usize,
24    },
25    /// The named slot is not the initialized start of a value.
26    Uninitialized {
27        /// Slot whose initialized value was requested.
28        slot: usize,
29    },
30    /// A width policy violated its contract by returning zero.
31    ZeroWidth {
32        /// Slot at which the invalid value was presented.
33        slot: usize,
34    },
35}
36
37/// Bounded indexed storage whose occupancy is measured in policy-defined units.
38///
39/// A value is loaded only through the first slot in its span. Replacing any
40/// unit of an initialized span first releases the entire old value, so partial
41/// overwrites are always observable as explicit uninitialization.
42pub struct SlotFile<P: ValueWidthPolicy> {
43    values: Vec<Option<P::Value>>,
44    occupancy: Vec<Option<Span>>,
45    _policy: PhantomData<P>,
46}
47
48impl<P: ValueWidthPolicy> SlotFile<P> {
49    /// Creates an uninitialized slot file using the control organ's admission limit.
50    pub fn new(limit: AdmissionLimit) -> Self {
51        Self {
52            values: (0..limit.0).map(|_| None).collect(),
53            occupancy: vec![None; limit.0],
54            _policy: PhantomData,
55        }
56    }
57
58    /// Returns the number of logical slots in the file.
59    pub fn limit(&self) -> usize {
60        self.occupancy.len()
61    }
62
63    /// Returns the initialized value beginning at `slot`.
64    pub fn load(&self, slot: usize) -> Result<&P::Value, SlotError> {
65        self.values
66            .get(slot)
67            .and_then(Option::as_ref)
68            .ok_or(SlotError::Uninitialized { slot })
69    }
70
71    /// Stores `value` beginning at `slot`, releasing every overlapped span.
72    pub fn store(&mut self, slot: usize, value: P::Value) -> Result<(), SlotError> {
73        let width = P::width(&value);
74        if width == 0 {
75            return Err(SlotError::ZeroWidth { slot });
76        }
77        let end = slot
78            .checked_add(width)
79            .filter(|end| *end <= self.limit())
80            .ok_or(SlotError::Overflow {
81                slot,
82                width,
83                limit: self.limit(),
84            })?;
85
86        let mut overlaps = self.occupancy[slot..end]
87            .iter()
88            .flatten()
89            .copied()
90            .collect::<Vec<_>>();
91        overlaps.sort_unstable_by_key(|span| span.start);
92        overlaps.dedup();
93        for span in overlaps {
94            self.release_span(span);
95        }
96
97        let span = Span { start: slot, width };
98        self.values[slot] = Some(value);
99        self.occupancy[slot..end].fill(Some(span));
100        Ok(())
101    }
102
103    /// Releases the initialized span containing `slot` and clears all its units.
104    pub fn release(&mut self, slot: usize) -> Result<P::Value, SlotError> {
105        let span = self
106            .occupancy
107            .get(slot)
108            .copied()
109            .flatten()
110            .ok_or(SlotError::Uninitialized { slot })?;
111        self.release_span(span)
112            .ok_or(SlotError::Uninitialized { slot })
113    }
114
115    /// Returns whether `slot` is occupied by any initialized span.
116    pub fn is_initialized(&self, slot: usize) -> bool {
117        self.occupancy.get(slot).is_some_and(Option::is_some)
118    }
119
120    /// Visits initialized values in ascending start-slot order.
121    pub fn visit_values(&self, mut visit: impl FnMut(&P::Value)) {
122        for value in self.values.iter().flatten() {
123            visit(value);
124        }
125    }
126
127    fn release_span(&mut self, span: Span) -> Option<P::Value> {
128        self.occupancy[span.start..span.start + span.width].fill(None);
129        self.values[span.start].take()
130    }
131}