use core::cmp::Ordering;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BlockSizeRelation {
NearLt,
NearEq,
NearGt,
Far,
}
impl BlockSizeRelation {
pub fn is_near(&self) -> bool {
!matches!(self, BlockSizeRelation::Far)
}
}
pub mod block_size {
use super::*;
pub const MIN: u32 = 3;
pub const NUM_VALID: usize = 31;
#[cfg(any(test, doc))]
pub(crate) const RANGE_LOG_VALID: core::ops::Range<u8> = 0..block_size::NUM_VALID as u8;
#[inline]
pub const fn is_valid(block_size: u32) -> bool {
(block_size % MIN == 0) && (block_size / MIN).is_power_of_two()
}
#[inline(always)]
pub const fn is_log_valid(log_block_size: u8) -> bool {
log_block_size < NUM_VALID as u8
}
#[inline(always)]
pub(crate) const fn from_log_internal_const(log_block_size: u8) -> u32 {
MIN << log_block_size
}
#[inline(always)]
pub(crate) fn from_log_internal(log_block_size: u8) -> u32 {
debug_assert!(is_log_valid(log_block_size));
from_log_internal_const(log_block_size)
}
#[cfg(feature = "unchecked")]
#[allow(unsafe_code)]
#[inline(always)]
pub unsafe fn from_log_unchecked(log_block_size: u8) -> u32 {
from_log_internal(log_block_size)
}
#[inline]
pub fn from_log(log_block_size: u8) -> Option<u32> {
is_log_valid(log_block_size).then(|| from_log_internal(log_block_size))
}
pub(crate) const BLOCK_SIZES_STR: [&str; NUM_VALID] = [
"3",
"6",
"12",
"24",
"48",
"96",
"192",
"384",
"768",
"1536",
"3072",
"6144",
"12288",
"24576",
"49152",
"98304",
"196608",
"393216",
"786432",
"1572864",
"3145728",
"6291456",
"12582912",
"25165824",
"50331648",
"100663296",
"201326592",
"402653184",
"805306368",
"1610612736",
"3221225472",
];
pub(crate) const MAX_BLOCK_SIZE_LEN_IN_CHARS: usize =
BLOCK_SIZES_STR[BLOCK_SIZES_STR.len() - 1].len();
const LOG_DEBRUIJN_CONSTANT: u32 = 0x017713ca;
#[inline(always)]
const fn debruijn_index(block_size: u32) -> usize {
(block_size.wrapping_mul(LOG_DEBRUIJN_CONSTANT) >> 27) as usize
}
#[rustfmt::skip]
const LOG_DEBRUIJN_TABLE: [u8; 32] = [
0x00, 0x01, 0x02, 0x06, 0x03, 0x0b, 0x07, 0x10,
0x04, 0x0e, 0x0c, 0x18, 0x08, 0x15, 0x11, 0x1a,
0x1e, 0x05, 0x0a, 0x0f, 0x0d, 0x17, 0x14, 0x19,
0x1d, 0x09, 0x16, 0x13, 0x1c, 0x12, 0x1b, 0xff,
];
#[inline(always)]
pub(crate) fn log_from_valid_internal(block_size: u32) -> u8 {
debug_assert!(is_valid(block_size));
LOG_DEBRUIJN_TABLE[debruijn_index(block_size)] }
#[cfg(feature = "unchecked")]
#[allow(unsafe_code)]
#[inline(always)]
pub unsafe fn log_from_valid_unchecked(block_size: u32) -> u8 {
log_from_valid_internal(block_size)
}
#[inline(always)]
pub fn log_from_valid(block_size: u32) -> u8 {
assert!(is_valid(block_size));
log_from_valid_internal(block_size)
}
#[inline(always)]
pub fn is_near(lhs: u8, rhs: u8) -> bool {
debug_assert!(is_log_valid(lhs));
debug_assert!(is_log_valid(rhs));
u32::wrapping_sub(lhs as u32, rhs as u32).wrapping_add(1) <= 2
}
#[inline(always)]
pub fn is_near_eq(lhs: u8, rhs: u8) -> bool {
debug_assert!(is_log_valid(lhs));
debug_assert!(is_log_valid(rhs));
lhs == rhs
}
#[inline(always)]
pub fn is_near_lt(lhs: u8, rhs: u8) -> bool {
debug_assert!(is_log_valid(lhs));
debug_assert!(is_log_valid(rhs));
(rhs as i32) - (lhs as i32) == 1
}
#[inline(always)]
pub fn is_near_gt(lhs: u8, rhs: u8) -> bool {
is_near_lt(rhs, lhs)
}
#[inline(always)]
pub fn compare_sizes(lhs: u8, rhs: u8) -> BlockSizeRelation {
debug_assert!(is_log_valid(lhs));
debug_assert!(is_log_valid(rhs));
match (lhs as i32) - (rhs as i32) {
-1 => BlockSizeRelation::NearLt,
0 => BlockSizeRelation::NearEq,
1 => BlockSizeRelation::NearGt,
_ => BlockSizeRelation::Far,
}
}
#[inline(always)]
pub fn cmp(lhs: u8, rhs: u8) -> Ordering {
debug_assert!(is_log_valid(lhs));
debug_assert!(is_log_valid(rhs));
u8::cmp(&lhs, &rhs)
}
}
pub mod block_hash {
pub const ALPHABET_SIZE: usize = 64;
pub const FULL_SIZE: usize = 64;
pub const HALF_SIZE: usize = FULL_SIZE / 2;
pub const MAX_SEQUENCE_SIZE: usize = 3;
pub const MIN_LCS_FOR_COMPARISON: usize = 7;
pub struct NumericWindows<'a> {
v: &'a [u8],
hash: u64,
}
pub struct IndexWindows<'a> {
inner: NumericWindows<'a>,
log_block_size: u8,
}
impl<'a> NumericWindows<'a> {
pub(crate) const ILOG2_OF_ALPHABETS: u32 = 6;
pub const BITS: u32 = (MIN_LCS_FOR_COMPARISON as u32) * Self::ILOG2_OF_ALPHABETS;
pub const MASK: u64 = (1u64 << Self::BITS).wrapping_sub(1);
#[inline]
pub(crate) fn new(block_hash: &'a [u8]) -> Self {
if block_hash.len() < MIN_LCS_FOR_COMPARISON {
Self { v: &[], hash: 0 }
} else {
Self {
v: &block_hash[MIN_LCS_FOR_COMPARISON - 1..],
hash: block_hash[..MIN_LCS_FOR_COMPARISON - 1]
.iter()
.enumerate()
.map(|(i, &value)| {
(value as u64)
<< (Self::ILOG2_OF_ALPHABETS
* (MIN_LCS_FOR_COMPARISON - 2 - i) as u32)
})
.fold(
0u64,
#[inline(always)]
|x, y| x | y,
),
}
}
}
}
impl<'a> IndexWindows<'a> {
pub(crate) const BLOCK_SIZE_BITS: u32 = 5;
pub const BITS: u32 = NumericWindows::BITS + Self::BLOCK_SIZE_BITS;
pub const MASK: u64 = (1u64 << Self::BITS).wrapping_sub(1);
#[inline]
pub(crate) fn new(block_hash: &'a [u8], log_block_size: u8) -> Self {
Self {
inner: NumericWindows::new(block_hash),
log_block_size,
}
}
}
impl Iterator for NumericWindows<'_> {
type Item = u64;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if let Some((&value, rest)) = self.v.split_first() {
self.hash = ((self.hash << Self::ILOG2_OF_ALPHABETS) | (value as u64)) & Self::MASK;
self.v = rest;
Some(self.hash)
} else {
None
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(self.v.len(), Some(self.v.len()))
}
}
impl Iterator for IndexWindows<'_> {
type Item = u64;
#[inline(always)]
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(
#[inline(always)]
|x| (x | ((self.log_block_size as u64) << NumericWindows::BITS)),
)
}
#[inline(always)]
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
impl ExactSizeIterator for NumericWindows<'_> {
#[inline]
fn len(&self) -> usize {
self.v.len()
}
}
impl ExactSizeIterator for IndexWindows<'_> {
#[inline(always)]
fn len(&self) -> usize {
self.inner.len()
}
}
#[allow(unsafe_code)]
#[cfg(all(feature = "unsafe-guarantee", feature = "unstable"))]
unsafe impl core::iter::TrustedLen for NumericWindows<'_> {}
#[allow(unsafe_code)]
#[cfg(all(feature = "unsafe-guarantee", feature = "unstable"))]
unsafe impl core::iter::TrustedLen for IndexWindows<'_> {}
impl core::iter::FusedIterator for NumericWindows<'_> {}
impl core::iter::FusedIterator for IndexWindows<'_> {}
}
pub struct BlockHashSize<const N: usize> {}
pub struct BlockHashSizes<const S1: usize, const S2: usize> {}
mod private {
use super::{block_hash, BlockHashSize, BlockHashSizes};
pub trait SealedBlockHashSize {}
impl SealedBlockHashSize for BlockHashSize<{ block_hash::FULL_SIZE }> {}
impl SealedBlockHashSize for BlockHashSize<{ block_hash::HALF_SIZE }> {}
pub trait SealedBlockHashSizes {}
impl SealedBlockHashSizes for BlockHashSizes<{ block_hash::FULL_SIZE }, { block_hash::FULL_SIZE }> {}
impl SealedBlockHashSizes for BlockHashSizes<{ block_hash::FULL_SIZE }, { block_hash::HALF_SIZE }> {}
}
pub trait ConstrainedBlockHashSize: private::SealedBlockHashSize {
const SIZE: usize;
}
impl<const SZ_BH: usize> ConstrainedBlockHashSize for BlockHashSize<SZ_BH>
where
BlockHashSize<SZ_BH>: private::SealedBlockHashSize,
{
const SIZE: usize = SZ_BH;
}
pub trait ConstrainedBlockHashSizes: private::SealedBlockHashSizes {
const MAX_BLOCK_HASH_SIZE_1: usize;
const MAX_BLOCK_HASH_SIZE_2: usize;
}
impl<const S1: usize, const S2: usize> ConstrainedBlockHashSizes for BlockHashSizes<S1, S2>
where
BlockHashSizes<S1, S2>: private::SealedBlockHashSizes,
{
const MAX_BLOCK_HASH_SIZE_1: usize = S1;
const MAX_BLOCK_HASH_SIZE_2: usize = S2;
}
#[doc(hidden)]
#[allow(clippy::unnecessary_cast)]
mod const_asserts {
use static_assertions::{const_assert, const_assert_eq, const_assert_ne};
use super::{block_hash, block_size};
const_assert_eq!(block_hash::ALPHABET_SIZE, 64);
const_assert!(block_hash::FULL_SIZE % 2 == 0);
const_assert_eq!(block_hash::FULL_SIZE, 64);
const_assert_eq!(block_size::MIN, 3);
const_assert_eq!(block_size::NUM_VALID, 31);
const_assert_eq!(block_hash::MAX_SEQUENCE_SIZE, 3);
const_assert_ne!(block_size::NUM_VALID as u8, u8::MAX);
const_assert!(usize::BITS < 32 || block_hash::MAX_SEQUENCE_SIZE < 0xffff_ffff as usize);
const_assert_ne!(block_hash::MAX_SEQUENCE_SIZE, usize::MAX);
const_assert!((block_size::MIN as u64) << (block_size::NUM_VALID - 1) <= u32::MAX as u64);
const_assert!((block_size::MIN as u64) << block_size::NUM_VALID > u32::MAX as u64);
const_assert!(block_hash::MIN_LCS_FOR_COMPARISON > 0);
const_assert!(block_hash::NumericWindows::BITS < u64::BITS);
const_assert!(block_hash::IndexWindows::BITS < u64::BITS);
const_assert!(block_hash::IndexWindows::BLOCK_SIZE_BITS < u64::BITS);
const_assert!(
(block_size::NUM_VALID as u64) < (1 << block_hash::IndexWindows::BLOCK_SIZE_BITS)
);
}
mod tests;