//! Pre-allocated storage with constant-time LRU tracking #![warn(missing_docs)] #![no_std] extern crate alloc; use alloc::boxed::Box; use core::{fmt, iter::FusedIterator, marker::PhantomData, ptr::addr_of_mut}; /// A random-access table that maintains an LRU list in constant time #[derive(Clone)] pub struct LruSlab { slots: Box<[Slot]>, /// Most recently used head: u32, /// Least recently used tail: u32, /// First unused free: u32, /// Number of occupied slots len: u32, } impl LruSlab { /// Create an empty [`LruSlab`] pub fn new() -> Self { Self::with_capacity(0) } /// Create an [`LruSlab`] that can store at least `capacity` elements without reallocating pub fn with_capacity(capacity: u32) -> Self { assert!(capacity != u32::MAX, "capacity too large"); Self { slots: (0..capacity) .map(|n| Slot { value: None, prev: NONE, next: if n + 1 == capacity { NONE } else { n + 1 }, }) .collect(), head: NONE, tail: NONE, free: if capacity == 0 { NONE } else { 0 }, len: 0, } } /// Whether no elements are stored pub fn is_empty(&self) -> bool { self.len == 0 } /// Number of elements stored pub fn len(&self) -> u32 { self.len } /// Number of elements that can be stored without reallocating pub fn capacity(&self) -> u32 { self.slots.len() as u32 } /// The slot that will be returned by the next call to `insert`, unless `remove` is called first pub fn vacant_key(&self) -> u32 { match self.free { NONE => self.capacity(), _ => self.free, } } /// Insert a value, returning the slot it was stored in /// /// The returned slot is marked as the most recently used. pub fn insert(&mut self, value: T) -> u32 { let id = match self.alloc() { Some(id) => id, None => { let len = self.capacity(); // Ensure `NONE` never becomes a real slot index let cap = 2u32.saturating_mul(len.max(2)).min(u32::MAX - 1); if cap == len { panic!("cannot store more than 2^32-2 elements"); } self.slots = self .slots .iter_mut() .map(|x| Slot { value: x.value.take(), next: x.next, prev: x.prev, }) .chain((len..cap).map(|n| Slot { value: None, prev: NONE, next: if n + 1 == cap { NONE } else { n + 1 }, })) .collect(); self.free = len + 1; len } }; let idx = id as usize; debug_assert!(self.slots[idx].value.is_none(), "corrupt free list"); self.slots[idx].value = Some(value); self.link_at_head(id); self.len += 1; id } /// Get the least recently used slot, if any pub fn lru(&self) -> Option { if self.tail == NONE { debug_assert_eq!(self.head, NONE); None } else { Some(self.tail) } } /// Remove the element stored in `slot`, returning it pub fn remove(&mut self, slot: u32) -> T { self.unlink(slot); self.slots[slot as usize].next = self.free; self.slots[slot as usize].prev = NONE; self.free = slot; self.len -= 1; self.slots[slot as usize] .value .take() .expect("removing empty slot") } /// Mark `slot` as the most recently used and access it uniquely pub fn get_mut(&mut self, slot: u32) -> &mut T { self.freshen(slot); self.peek_mut(slot) } /// Access `slot` without marking it as most recently used pub fn peek(&self, slot: u32) -> &T { self.slots[slot as usize].value.as_ref().unwrap() } /// Access `slot` uniquely without marking it as most recently used pub fn peek_mut(&mut self, slot: u32) -> &mut T { self.slots[slot as usize].value.as_mut().unwrap() } /// Walk the container from most to least recently used pub fn iter(&self) -> Iter<'_, T> { let state = IterState::new(self); Iter { slots: &self.slots[..], state, } } /// Walk the container from most to least recently used pub fn iter_mut(&mut self) -> IterMut<'_, T> { let state = IterState::new(self); IterMut { slots: self.slots[..].as_mut_ptr().cast(), state, _marker: PhantomData, } } /// Remove a slot from the freelist fn alloc(&mut self) -> Option { if self.free == NONE { return None; } let slot = self.free; self.free = self.slots[slot as usize].next; Some(slot) } /// Mark `slot` as the most recently used fn freshen(&mut self, slot: u32) { if self.slots[slot as usize].prev == NONE { // This is already the freshest slot, so we don't need to do anything debug_assert_eq!(self.head, slot, "corrupt LRU list"); return; } self.unlink(slot); self.link_at_head(slot); } /// Add a link to the head of the list fn link_at_head(&mut self, slot: u32) { let idx = slot as usize; if self.head == NONE { // List was empty self.slots[idx].next = NONE; self.tail = slot; } else { self.slots[idx].next = self.head; self.slots[self.head as usize].prev = slot; } self.slots[idx].prev = NONE; self.head = slot; } /// Remove a link from anywhere in the list fn unlink(&mut self, slot: u32) { let idx = slot as usize; if self.slots[idx].prev != NONE { self.slots[self.slots[idx].prev as usize].next = self.slots[idx].next; } else { self.head = self.slots[idx].next; } if self.slots[idx].next != NONE { self.slots[self.slots[idx].next as usize].prev = self.slots[idx].prev; } else { // This was the tail self.tail = self.slots[idx].prev; } } } impl Default for LruSlab { fn default() -> Self { Self::new() } } impl FromIterator for LruSlab { fn from_iter>(iter: I) -> Self { let iter = iter.into_iter(); let mut slab = LruSlab::with_capacity(u32::try_from(iter.size_hint().0).unwrap()); for x in iter { slab.insert(x); } slab } } impl<'a, T> IntoIterator for &'a LruSlab { type Item = (u32, &'a T); type IntoIter = Iter<'a, T>; fn into_iter(self) -> Self::IntoIter { self.iter() } } impl<'a, T> IntoIterator for &'a mut LruSlab { type Item = (u32, &'a mut T); type IntoIter = IterMut<'a, T>; fn into_iter(self) -> Self::IntoIter { self.iter_mut() } } impl fmt::Debug for LruSlab { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_map().entries(self).finish() } } #[derive(Clone)] struct Slot { value: Option, /// Next slot in the LRU or free list next: u32, /// Previous slot in the LRU list; NONE when free prev: u32, } const NONE: u32 = u32::MAX; /// Iterator over elements of an [`LruSlab`], from most to least recently used pub struct Iter<'a, T> { slots: &'a [Slot], state: IterState, } impl<'a, T> Iterator for Iter<'a, T> { type Item = (u32, &'a T); fn next(&mut self) -> Option<(u32, &'a T)> { let idx = self.state.next(|i| self.slots[i as usize].next)?; let result = self.slots[idx as usize] .value .as_ref() .expect("corrupt LRU list"); Some((idx, result)) } fn size_hint(&self) -> (usize, Option) { (self.state.len as usize, Some(self.state.len as usize)) } } impl<'a, T> DoubleEndedIterator for Iter<'a, T> { fn next_back(&mut self) -> Option<(u32, &'a T)> { let idx = self.state.next_back(|i| self.slots[i as usize].prev)?; let result = self.slots[idx as usize] .value .as_ref() .expect("corrupt LRU list"); Some((idx, result)) } } impl ExactSizeIterator for Iter<'_, T> { fn len(&self) -> usize { self.state.len as usize } } impl FusedIterator for Iter<'_, T> {} /// Iterator over mutable elements of an [`LruSlab`], from most to least recently used pub struct IterMut<'a, T> { slots: *mut Slot, state: IterState, _marker: PhantomData<&'a mut [Slot]>, } impl<'a, T> Iterator for IterMut<'a, T> { type Item = (u32, &'a mut T); fn next(&mut self) -> Option<(u32, &'a mut T)> { // Safety: `next` returns unique in-bounds indices, and no live references overlap with any // `next` field unsafe { let idx = self .state .next(|i| *addr_of_mut!((*self.slots.add(i as usize)).next))?; let result = (*addr_of_mut!((*self.slots.add(idx as usize)).value)) .as_mut() .expect("corrupt LRU list"); Some((idx, result)) } } fn size_hint(&self) -> (usize, Option) { (self.state.len as usize, Some(self.state.len as usize)) } } impl<'a, T> DoubleEndedIterator for IterMut<'a, T> { fn next_back(&mut self) -> Option<(u32, &'a mut T)> { // Safety: `next_back` returns unique in-bounds indices, and no live references overlap with // any `prev` field unsafe { let idx = self .state .next_back(|i| *addr_of_mut!((*self.slots.add(i as usize)).prev))?; let result = (*addr_of_mut!((*self.slots.add(idx as usize)).value)) .as_mut() .expect("corrupt LRU list"); Some((idx, result)) } } } impl ExactSizeIterator for IterMut<'_, T> { fn len(&self) -> usize { self.state.len as usize } } impl FusedIterator for IterMut<'_, T> {} struct IterState { head: u32, tail: u32, len: u32, } impl IterState { fn new(slab: &LruSlab) -> Self { Self { head: slab.head, tail: slab.tail, len: slab.len, } } fn next(&mut self, get_next: impl Fn(u32) -> u32) -> Option { if self.len == 0 { return None; } let idx = self.head; self.head = get_next(idx); self.len -= 1; Some(idx) } fn next_back(&mut self, get_prev: impl Fn(u32) -> u32) -> Option { if self.len == 0 { return None; } let idx = self.tail; self.tail = get_prev(idx); self.len -= 1; Some(idx) } } #[cfg(test)] mod tests { use alloc::{format, string::String, vec::Vec}; use super::*; #[test] fn lru_order() { let mut cache = LruSlab::new(); let b = cache.insert('b'); assert_eq!(cache.iter().map(|(_, x)| x).collect::(), "b"); let _a = cache.insert('a'); assert_eq!(cache.iter().map(|(_, x)| x).collect::(), "ab"); let d = cache.insert('d'); assert_eq!(cache.iter().map(|(_, x)| x).collect::(), "dab"); let c = cache.insert('c'); assert_eq!(cache.iter().map(|(_, x)| x).collect::(), "cdab"); let e = cache.insert('e'); assert_eq!(cache.iter().map(|(_, x)| x).collect::(), "ecdab"); cache.get_mut(b); cache.get_mut(c); cache.get_mut(d); cache.get_mut(e); assert_eq!(cache.remove(cache.lru().unwrap()), 'a'); assert_eq!(cache.remove(cache.lru().unwrap()), 'b'); assert_eq!(cache.remove(cache.lru().unwrap()), 'c'); assert_eq!(cache.remove(cache.lru().unwrap()), 'd'); assert_eq!(cache.remove(cache.lru().unwrap()), 'e'); assert!(cache.lru().is_none()); } #[test] fn slot_reuse() { let mut cache = LruSlab::new(); let a = cache.insert('a'); cache.remove(a); let a_prime = cache.insert('a'); assert_eq!(a, a_prime); assert_eq!(cache.len(), 1); } #[test] fn debug() { let slab = ['a', 'b'].into_iter().collect::>(); assert_eq!(format!("{:?}", slab), "{1: 'b', 0: 'a'}"); } #[test] fn iter_reverse() { let slab = ['a', 'b'].into_iter().collect::>(); let mut double_reversed = slab.iter().rev().collect::>(); double_reversed.reverse(); assert_eq!(slab.iter().collect::>(), double_reversed); } #[test] fn vacant_key() { let mut slab = LruSlab::new(); assert_eq!(slab.vacant_key(), 0); slab.insert(()); assert_eq!(slab.vacant_key(), 1); slab.remove(0); assert_eq!(slab.vacant_key(), 0); } }