perf(kernel): kernel static all-zero -> .bss (image 812K -> 173K): domain::Slot (repr(u8) Empty=0) for tables, zero generation counters, core ids by position, pool/platform installed at boot; Option-backed Slot under kani (cbmc speed); build fails if .data > 16K

This commit is contained in:
2026-10-28 13:01:52 +01:00
parent 419b715a1f
commit a2d4a7d085
24 changed files with 320 additions and 116 deletions

144
crates/domain/src/slot.rs Normal file
View File

@@ -0,0 +1,144 @@
//! A table entry that is empty or holds a value, like `Option`, but whose
//! empty state is guaranteed to be all zero bytes. The kernel's tables are
//! one static; if every empty entry is zero, the whole static starts zero
//! and costs nothing in the kernel image (`.bss`). `Option` makes no such
//! promise (its `None` is whatever the compiler picks).
/// `#[repr(u8)]` fixes the layout: a tag byte, `Empty` being 0, then the
/// value (left uninitialised when empty).
#[cfg(not(kani))]
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Slot<T> {
#[default]
Empty = 0,
Full(T) = 1,
}
/// For the proofs, the same behaviour over `Option`: only the byte layout
/// differs, which no proof depends on, and CBMC is far faster on `Option`
/// than on an enum with an uninitialised payload.
#[cfg(kani)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Slot<T>(Option<T>);
#[cfg(kani)]
impl<T> Default for Slot<T> {
fn default() -> Self {
Self(None)
}
}
#[cfg(kani)]
#[allow(non_upper_case_globals, non_snake_case)]
impl<T> Slot<T> {
pub const Empty: Self = Self(None);
pub const fn Full(value: T) -> Self {
Self(Some(value))
}
pub const fn is_full(&self) -> bool {
self.0.is_some()
}
pub const fn as_ref(&self) -> Option<&T> {
self.0.as_ref()
}
pub fn as_mut(&mut self) -> Option<&mut T> {
self.0.as_mut()
}
pub fn take(&mut self) -> Option<T> {
self.0.take()
}
pub fn set(&mut self, value: T) {
self.0 = Some(value);
}
}
#[cfg(kani)]
impl<T: Copy> Slot<T> {
pub const fn get(&self) -> Option<T> {
self.0
}
}
#[cfg(kani)]
impl<T> From<Option<T>> for Slot<T> {
fn from(o: Option<T>) -> Self {
Self(o)
}
}
#[cfg(not(kani))]
impl<T> Slot<T> {
pub const fn is_full(&self) -> bool {
matches!(self, Slot::Full(_))
}
pub const fn as_ref(&self) -> Option<&T> {
match self {
Slot::Full(v) => Some(v),
Slot::Empty => None,
}
}
pub fn as_mut(&mut self) -> Option<&mut T> {
match self {
Slot::Full(v) => Some(v),
Slot::Empty => None,
}
}
/// The value, leaving the slot empty.
pub fn take(&mut self) -> Option<T> {
match core::mem::replace(self, Slot::Empty) {
Slot::Full(v) => Some(v),
Slot::Empty => None,
}
}
pub fn set(&mut self, value: T) {
*self = Slot::Full(value);
}
}
#[cfg(not(kani))]
impl<T: Copy> Slot<T> {
pub const fn get(&self) -> Option<T> {
match self {
Slot::Full(v) => Some(*v),
Slot::Empty => None,
}
}
}
#[cfg(not(kani))]
impl<T> From<Option<T>> for Slot<T> {
fn from(o: Option<T>) -> Self {
match o {
Some(v) => Slot::Full(v),
None => Slot::Empty,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_slot_behaves_like_an_option() {
let mut s: Slot<u32> = Slot::Empty;
assert!(!s.is_full() && s.get().is_none());
s.set(5);
*s.as_mut().unwrap() += 1;
assert_eq!(s.get(), Some(6));
assert_eq!(s.take(), Some(6));
assert_eq!(s, Slot::Empty);
assert_eq!(Slot::from(Some(1)), Slot::Full(1));
}
}