pipecircuit 0.0.25

Correct. Robust. Fast. Portable. Pick four.
Documentation
use super::{SlotLocation, StableSlot};
use crate::storage::warp_table::{__private::invariant_violation, ColumnLease};
use core::ops::{Deref, DerefMut};

pub trait StableSlotColumn<T>: Deref<Target = [T]> + DerefMut {
    fn append_admitted(&mut self, value: T);
    fn remove_admitted(&mut self, row: usize);
}

#[cfg(feature = "alloc")]
impl<T> StableSlotColumn<T> for alloc::vec::Vec<T> {
    fn append_admitted(&mut self, value: T) {
        self.push(value);
    }
    fn remove_admitted(&mut self, row: usize) {
        self.swap_remove(row);
    }
}

impl<T> StableSlotColumn<T> for ColumnLease<'_, T> {
    fn append_admitted(&mut self, value: T) {
        if self.push(value).is_err() {
            invariant_violation(StableSlotsStorageError::ColumnFull);
        }
    }
    fn remove_admitted(&mut self, row: usize) {
        if self.swap_remove(row).is_none() {
            invariant_violation(StableSlotsStorageError::RowAbsent { row });
        }
    }
}

pub trait StableSlotsBacking {
    type Rows: StableSlotColumn<StableSlot>;
    type Locations: StableSlotColumn<SlotLocation>;
}

#[cfg(feature = "alloc")]
pub enum AllocatedStableSlots {}
#[cfg(feature = "alloc")]
impl StableSlotsBacking for AllocatedStableSlots {
    type Rows = alloc::vec::Vec<StableSlot>;
    type Locations = alloc::vec::Vec<SlotLocation>;
}

pub struct CallerStableSlots<'storage> {
    pub rows: ColumnLease<'storage, StableSlot>,
    pub locations: ColumnLease<'storage, SlotLocation>,
}
impl<'storage> StableSlotsBacking for CallerStableSlots<'storage> {
    type Rows = ColumnLease<'storage, StableSlot>;
    type Locations = ColumnLease<'storage, SlotLocation>;
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum StableSlotsStorageError {
    CapacityUnrepresentable { maximum_rows: usize },
    ColumnsNotEmpty,
    InsufficientRows { required: usize, available: usize },
    InsufficientLocations { required: usize, available: usize },
    ColumnFull,
    RowAbsent { row: usize },
}
pub struct StableSlotsStorageRefusal<'storage> {
    pub storage: CallerStableSlots<'storage>,
    pub reason: StableSlotsStorageError,
}

impl<'storage> super::StableSlots<CallerStableSlots<'storage>> {
    pub fn from_storage(
        maximum_rows: usize,
        storage: CallerStableSlots<'storage>,
    ) -> Result<Self, StableSlotsStorageRefusal<'storage>> {
        let refusal = if u32::try_from(maximum_rows).is_err() {
            Some(StableSlotsStorageError::CapacityUnrepresentable { maximum_rows })
        } else if !storage.rows.is_empty() || !storage.locations.is_empty() {
            Some(StableSlotsStorageError::ColumnsNotEmpty)
        } else if storage.rows.capacity() < maximum_rows {
            Some(StableSlotsStorageError::InsufficientRows {
                required: maximum_rows,
                available: storage.rows.capacity(),
            })
        } else if storage.locations.capacity() < maximum_rows {
            Some(StableSlotsStorageError::InsufficientLocations {
                required: maximum_rows,
                available: storage.locations.capacity(),
            })
        } else {
            None
        };
        if let Some(reason) = refusal {
            return Err(StableSlotsStorageRefusal { storage, reason });
        }
        Ok(Self {
            maximum_rows,
            row_to_slot: storage.rows,
            slot_locations: storage.locations,
            first_free: None,
        })
    }
}

#[cfg(test)]
mod tests {
    extern crate std;
    use super::*;
    use core::{mem::MaybeUninit, panic::AssertUnwindSafe};
    use std::panic::catch_unwind;

    #[test]
    fn borrowed_slot_admission_returns_every_refused_column() -> Result<(), &'static str> {
        for (rows, locations, requested, expected) in [
            (
                0,
                1,
                1,
                StableSlotsStorageError::InsufficientRows {
                    required: 1,
                    available: 0,
                },
            ),
            (
                1,
                0,
                1,
                StableSlotsStorageError::InsufficientLocations {
                    required: 1,
                    available: 0,
                },
            ),
            #[cfg(target_pointer_width = "64")]
            (
                1,
                1,
                usize::MAX,
                StableSlotsStorageError::CapacityUnrepresentable {
                    maximum_rows: usize::MAX,
                },
            ),
        ] {
            let mut row_storage = [MaybeUninit::uninit(); 1];
            let mut location_storage = [const { MaybeUninit::uninit() }; 1];
            let rows = ColumnLease::new(row_storage.get_mut(..rows).ok_or("rows")?);
            let locations =
                ColumnLease::new(location_storage.get_mut(..locations).ok_or("locations")?);
            let storage = CallerStableSlots { rows, locations };
            let Err(refusal) = super::super::StableSlots::from_storage(requested, storage) else {
                return Err("accepted invalid columns");
            };
            assert_eq!(refusal.reason, expected);
            assert!(refusal.storage.rows.is_empty());
            assert!(refusal.storage.locations.is_empty());
        }
        let mut rows = [MaybeUninit::uninit(); 1];
        let mut locations = [const { MaybeUninit::uninit() }; 1];
        let mut storage = CallerStableSlots {
            rows: ColumnLease::new(&mut rows),
            locations: ColumnLease::new(&mut locations),
        };
        storage.rows.push(StableSlot::new(0)).map_err(|_| "push")?;
        let Err(refusal) = super::super::StableSlots::from_storage(1, storage) else {
            return Err("accepted populated columns");
        };
        assert_eq!(refusal.reason, StableSlotsStorageError::ColumnsNotEmpty);
        assert_eq!(refusal.storage.rows.as_slice(), &[StableSlot::new(0)]);
        Ok(())
    }

    #[test]
    fn invalid_admitted_column_operations_cannot_silently_lose_members() {
        let mut storage = [];
        let mut column = ColumnLease::<u32>::new(&mut storage);
        assert!(catch_unwind(AssertUnwindSafe(|| column.append_admitted(7))).is_err());
        assert!(catch_unwind(AssertUnwindSafe(|| column.remove_admitted(0))).is_err());
        assert!(column.is_empty());
    }
}