#![no_std]
#[cfg(any(feature = "alloc", kani))]
extern crate alloc;
#[cfg(kani)]
extern crate kani;
#[cfg(any(feature = "alloc", kani))]
use alloc::vec::Vec;
use core::{error::Error, fmt, hash::Hash, iter::FusedIterator, marker::PhantomData};
use zerocopy::{
FromBytes, Immutable, IntoBytes, KnownLayout,
byteorder::{LE, U16, U32, U64},
};
mod sealed {
pub trait LayoutIndex {}
pub trait ZerocopyWord {}
pub trait LayoutSnapshotWord {}
pub trait SnapshotWidth {}
pub trait Axis {}
}
pub trait LayoutIndex:
sealed::LayoutIndex + Copy + Eq + Ord + fmt::Debug + fmt::Display + Hash + Sized
{
const ZERO: Self;
fn to_usize(self) -> Option<usize>;
fn from_usize(value: usize) -> Option<Self>;
}
macro_rules! impl_layout_index {
($index:ty) => {
impl sealed::LayoutIndex for $index {}
impl LayoutIndex for $index {
const ZERO: Self = 0;
fn to_usize(self) -> Option<usize> {
usize::try_from(self).ok()
}
fn from_usize(value: usize) -> Option<Self> {
Self::try_from(value).ok()
}
}
};
}
impl_layout_index!(u16);
impl_layout_index!(u32);
impl_layout_index!(u64);
impl sealed::LayoutIndex for usize {}
impl LayoutIndex for usize {
const ZERO: Self = 0;
fn to_usize(self) -> Option<usize> {
Some(self)
}
fn from_usize(value: usize) -> Option<Self> {
Some(value)
}
}
pub trait ZerocopyWord: sealed::ZerocopyWord + Copy {
fn read_as_usize(self) -> Option<usize>;
}
macro_rules! impl_native_zerocopy_word {
($word:ty) => {
impl sealed::ZerocopyWord for $word {}
impl ZerocopyWord for $word {
fn read_as_usize(self) -> Option<usize> {
usize::try_from(self).ok()
}
}
};
}
impl_native_zerocopy_word!(u16);
impl_native_zerocopy_word!(u32);
impl_native_zerocopy_word!(u64);
impl_native_zerocopy_word!(usize);
macro_rules! impl_le_zerocopy_word {
($word:ty) => {
impl sealed::ZerocopyWord for $word {}
impl ZerocopyWord for $word {
fn read_as_usize(self) -> Option<usize> {
usize::try_from(<$word>::get(self)).ok()
}
}
};
}
impl_le_zerocopy_word!(U16<LE>);
impl_le_zerocopy_word!(U32<LE>);
impl_le_zerocopy_word!(U64<LE>);
pub trait LayoutWord: Copy + ZerocopyWord {
type Index: LayoutIndex;
fn get(self) -> Self::Index;
}
macro_rules! impl_native_layout_word {
($word:ty) => {
impl LayoutWord for $word {
type Index = $word;
fn get(self) -> Self::Index {
self
}
}
};
}
impl_native_layout_word!(u16);
impl_native_layout_word!(u32);
impl_native_layout_word!(u64);
impl_native_layout_word!(usize);
macro_rules! impl_le_layout_word {
($word:ty, $index:ty) => {
impl LayoutWord for $word {
type Index = $index;
fn get(self) -> Self::Index {
<$word>::get(self)
}
}
};
}
impl_le_layout_word!(U16<LE>, u16);
impl_le_layout_word!(U32<LE>, u32);
impl_le_layout_word!(U64<LE>, u64);
pub trait LayoutSnapshotWord:
sealed::LayoutSnapshotWord + LayoutWord + FromBytes + Immutable + IntoBytes + KnownLayout
{
}
macro_rules! impl_layout_snapshot_word {
($word:ty) => {
impl sealed::LayoutSnapshotWord for $word {}
impl LayoutSnapshotWord for $word {}
};
}
impl_layout_snapshot_word!(U16<LE>);
impl_layout_snapshot_word!(U32<LE>);
impl_layout_snapshot_word!(U64<LE>);
pub trait SnapshotWidth: sealed::SnapshotWidth + LayoutIndex {
type LittleEndianWord: LayoutSnapshotWord<Index = Self>;
fn to_le_word(self) -> Self::LittleEndianWord;
fn from_le_word(word: Self::LittleEndianWord) -> Self;
}
macro_rules! impl_snapshot_width {
($index:ty, $word:ty) => {
impl sealed::SnapshotWidth for $index {}
impl SnapshotWidth for $index {
type LittleEndianWord = $word;
fn to_le_word(self) -> Self::LittleEndianWord {
<$word>::new(self)
}
fn from_le_word(word: Self::LittleEndianWord) -> Self {
<$word>::get(word)
}
}
};
}
impl_snapshot_width!(u16, U16<LE>);
impl_snapshot_width!(u32, U32<LE>);
impl_snapshot_width!(u64, U64<LE>);
pub trait Axis: sealed::Axis {}
macro_rules! define_axis {
($(#[$doc:meta])* $name:ident) => {
$(#[$doc])*
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct $name;
impl sealed::Axis for $name {}
impl Axis for $name {}
};
}
define_axis!(
NodeAxis
);
define_axis!(
EdgeAxis
);
define_axis!(
VertexAxis
);
define_axis!(
HyperedgeAxis
);
define_axis!(
IncidenceAxis
);
pub struct LocalId<A, Width> {
value: Width,
axis: PhantomData<fn() -> A>,
}
impl<A, Width> LocalId<A, Width> {
#[inline]
#[must_use]
pub const fn new(value: Width) -> Self {
Self {
value,
axis: PhantomData,
}
}
}
impl<A, Width: Copy> LocalId<A, Width> {
#[inline]
#[must_use]
pub const fn get(self) -> Width {
self.value
}
}
impl<A, Width> From<Width> for LocalId<A, Width> {
#[inline]
fn from(value: Width) -> Self {
Self::new(value)
}
}
impl<A, Width: Clone> Clone for LocalId<A, Width> {
#[inline]
fn clone(&self) -> Self {
Self {
value: self.value.clone(),
axis: PhantomData,
}
}
}
impl<A, Width: Copy> Copy for LocalId<A, Width> {}
impl<A, Width: PartialEq> PartialEq for LocalId<A, Width> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.value == other.value
}
}
impl<A, Width: Eq> Eq for LocalId<A, Width> {}
impl<A, Width: PartialOrd> PartialOrd for LocalId<A, Width> {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
self.value.partial_cmp(&other.value)
}
}
impl<A, Width: Ord> Ord for LocalId<A, Width> {
#[inline]
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.value.cmp(&other.value)
}
}
impl<A, Width: Hash> Hash for LocalId<A, Width> {
#[inline]
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.value.hash(state);
}
}
impl<A, Width: fmt::Debug> fmt::Debug for LocalId<A, Width> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.debug_tuple("LocalId").field(&self.value).finish()
}
}
impl<A, Width: fmt::Display> fmt::Display for LocalId<A, Width> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
self.value.fmt(formatter)
}
}
#[derive(Clone, Debug)]
pub struct IdSlice<'view, W: LayoutWord, Id> {
inner: core::slice::Iter<'view, W>,
id: PhantomData<fn() -> Id>,
}
impl<'view, W: LayoutWord, Id> IdSlice<'view, W, Id> {
#[inline]
#[must_use]
pub fn new(slice: &'view [W]) -> Self {
Self {
inner: slice.iter(),
id: PhantomData,
}
}
}
impl<W: LayoutWord, Id> Iterator for IdSlice<'_, W, Id>
where
Id: From<W::Index>,
{
type Item = Id;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|word| Id::from(word.get()))
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
impl<W: LayoutWord, Id> ExactSizeIterator for IdSlice<'_, W, Id>
where
Id: From<W::Index>,
{
#[inline]
fn len(&self) -> usize {
self.inner.len()
}
}
impl<W: LayoutWord, Id> DoubleEndedIterator for IdSlice<'_, W, Id>
where
Id: From<W::Index>,
{
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
self.inner.next_back().map(|word| Id::from(word.get()))
}
}
impl<W: LayoutWord, Id> FusedIterator for IdSlice<'_, W, Id> where Id: From<W::Index> {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum IdOutOfBounds {
UsizeOverflow,
OutOfRange {
slot: usize,
count: usize,
},
}
impl fmt::Display for IdOutOfBounds {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UsizeOverflow => formatter.write_str("ID value did not fit in usize"),
Self::OutOfRange { slot, count } => write!(
formatter,
"ID slot {slot} is not less than the dense bound {count}"
),
}
}
}
impl Error for IdOutOfBounds {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum OffsetOverflow {
IndexOverflow {
value: usize,
},
}
impl fmt::Display for OffsetOverflow {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::IndexOverflow { value } => {
write!(
formatter,
"value {value} does not fit in the target index width"
)
}
}
}
}
impl Error for OffsetOverflow {}
#[inline]
pub fn map_offset_overflow<E>(error: OffsetOverflow, on_overflow: impl FnOnce(usize) -> E) -> E {
match error {
OffsetOverflow::IndexOverflow { value } => on_overflow(value),
}
}
#[inline]
pub fn id_to_slot<I: LayoutIndex>(id: I, count: usize) -> Result<usize, IdOutOfBounds> {
let slot = id.to_usize().ok_or(IdOutOfBounds::UsizeOverflow)?;
if slot < count {
Ok(slot)
} else {
Err(IdOutOfBounds::OutOfRange { slot, count })
}
}
#[inline]
#[must_use]
pub fn slot_or_max<I: LayoutIndex>(id: I) -> usize {
id.to_usize().unwrap_or(usize::MAX)
}
#[inline]
pub fn index_from_usize<O: LayoutIndex>(value: usize) -> Result<O, OffsetOverflow> {
O::from_usize(value).ok_or(OffsetOverflow::IndexOverflow { value })
}
#[inline]
#[must_use]
pub fn index_to_usize_validated<I: LayoutIndex>(value: I) -> Option<usize> {
value.to_usize()
}
#[inline]
#[must_use]
pub fn usize_to_index_validated<I: LayoutIndex>(value: usize) -> Option<I> {
I::from_usize(value)
}
#[cfg(any(feature = "alloc", kani))]
#[must_use]
pub fn slice_to_le<W: SnapshotWidth>(values: &[W]) -> Vec<W::LittleEndianWord> {
values.iter().copied().map(W::to_le_word).collect()
}
#[cfg(any(feature = "alloc", kani))]
pub fn build_offset_index<O, T>(buckets: Vec<Vec<T>>) -> Result<(Vec<O>, Vec<T>), OffsetOverflow>
where
O: LayoutIndex,
{
let mut offsets = Vec::with_capacity(buckets.len() + 1);
let mut items: Vec<T> = Vec::new();
offsets.push(index_from_usize::<O>(0)?);
for bucket in buckets {
items.extend(bucket);
offsets.push(index_from_usize::<O>(items.len())?);
}
Ok((offsets, items))
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum OffsetIntegrityIssue {
Length {
expected: usize,
actual: usize,
},
CountOverflow {
count: usize,
},
FirstNonZero {
actual: usize,
},
NonMonotonic {
index: usize,
previous: usize,
actual: usize,
},
FinalMismatch {
final_offset: usize,
value_len: usize,
},
ValueOutOfRange {
index: usize,
value: usize,
bound: usize,
},
UsizeOverflow {
index: usize,
},
}
impl fmt::Display for OffsetIntegrityIssue {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Length { expected, actual } => write!(
formatter,
"offsets length {actual} does not match expected {expected}"
),
Self::CountOverflow { count } => {
write!(formatter, "element count {count} + 1 overflows usize")
}
Self::FirstNonZero { actual } => {
write!(formatter, "first offset {actual} must be zero")
}
Self::NonMonotonic {
index,
previous,
actual,
} => write!(
formatter,
"offsets[{index}] = {actual} is less than offsets[{}] = {previous}",
index - 1
),
Self::FinalMismatch {
final_offset,
value_len,
} => write!(
formatter,
"final offset {final_offset} does not match values length {value_len}"
),
Self::ValueOutOfRange {
index,
value,
bound,
} => write!(
formatter,
"values[{index}] = {value} is not less than bound {bound}"
),
Self::UsizeOverflow { index } => write!(
formatter,
"word at slice index {index} did not fit in usize"
),
}
}
}
impl Error for OffsetIntegrityIssue {}
pub fn check_offsets_monotonic<W: ZerocopyWord>(offsets: &[W]) -> Result<(), OffsetIntegrityIssue> {
let mut previous: usize = 0;
for (index, word) in offsets.iter().copied().enumerate() {
let offset = word
.read_as_usize()
.ok_or(OffsetIntegrityIssue::UsizeOverflow { index })?;
if index == 0 {
if offset != 0 {
return Err(OffsetIntegrityIssue::FirstNonZero { actual: offset });
}
} else if offset < previous {
return Err(OffsetIntegrityIssue::NonMonotonic {
index,
previous,
actual: offset,
});
}
previous = offset;
}
Ok(())
}
pub fn check_value_range<W: ZerocopyWord>(
values: &[W],
bound: usize,
) -> Result<(), OffsetIntegrityIssue> {
for (index, word) in values.iter().copied().enumerate() {
let value = word
.read_as_usize()
.ok_or(OffsetIntegrityIssue::UsizeOverflow { index })?;
if value >= bound {
return Err(OffsetIntegrityIssue::ValueOutOfRange {
index,
value,
bound,
});
}
}
Ok(())
}
pub fn check_offset_section<W: ZerocopyWord>(
offsets: &[W],
expected_count: usize,
value_len: usize,
) -> Result<(), OffsetIntegrityIssue> {
let Some(expected) = expected_count.checked_add(1) else {
return Err(OffsetIntegrityIssue::CountOverflow {
count: expected_count,
});
};
if offsets.len() != expected {
return Err(OffsetIntegrityIssue::Length {
expected,
actual: offsets.len(),
});
}
check_offsets_monotonic(offsets)?;
let final_index = offsets.len() - 1;
let final_offset = offsets[final_index]
.read_as_usize()
.ok_or(OffsetIntegrityIssue::UsizeOverflow { index: final_index })?;
if final_offset != value_len {
return Err(OffsetIntegrityIssue::FinalMismatch {
final_offset,
value_len,
});
}
Ok(())
}
#[cfg(kani)]
mod proofs;