use std::alloc::{Layout, alloc, alloc_zeroed, dealloc};
use std::mem::{align_of, needs_drop, size_of};
use std::panic::UnwindSafe;
use std::ptr::NonNull;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering::{Acquire, Relaxed};
use sdd::{AtomicRaw, RawPtr};
use super::bucket::{BUCKET_LEN, Bucket, INDEX, LruList};
use crate::Guard;
use crate::data_block::DataBlock;
use crate::exit_guard::ExitGuard;
use crate::utils::{fake_ref, get_owned, unwrap_unchecked};
pub struct BucketArray<K, V, L: LruList, const TYPE: char> {
buckets: NonNull<Bucket<K, V, L, TYPE>>,
data_blocks: NonNull<DataBlock<K, V, BUCKET_LEN>>,
array_len: usize,
hash_offset: u8,
sample_size: u8,
bucket_ptr_offset: u16,
linked_array: AtomicRaw<BucketArray<K, V, L, TYPE>>,
num_cleared_buckets: AtomicUsize,
}
impl<K, V, L: LruList, const TYPE: char> BucketArray<K, V, L, TYPE> {
pub(crate) fn new(
capacity: usize,
linked_array: AtomicRaw<BucketArray<K, V, L, TYPE>>,
) -> Self {
let adjusted_capacity = capacity
.min(1_usize << (usize::BITS - 2))
.next_power_of_two()
.max(Self::minimum_capacity());
let array_len = adjusted_capacity / BUCKET_LEN;
let log2_array_len = u8::try_from(array_len.trailing_zeros()).unwrap_or(0);
assert_eq!(1_usize << log2_array_len, array_len);
let sample_size = log2_array_len.next_power_of_two();
let alignment = align_of::<Bucket<K, V, L, TYPE>>();
let layout = Self::bucket_array_layout(array_len);
unsafe {
let Some(unaligned_bucket_array_ptr) = NonNull::new(alloc_zeroed(layout)) else {
panic!("Memory allocation failed: {layout:?}");
};
let bucket_array_ptr_offset = unaligned_bucket_array_ptr.align_offset(alignment);
assert_eq!(
(unaligned_bucket_array_ptr.addr().get() + bucket_array_ptr_offset) % alignment,
0
);
#[allow(clippy::cast_ptr_alignment)] let buckets = unaligned_bucket_array_ptr
.add(bucket_array_ptr_offset)
.cast::<Bucket<K, V, L, TYPE>>();
let bucket_array_ptr_offset = u16::try_from(bucket_array_ptr_offset).unwrap_or(0);
let alloc_guard = ExitGuard::new((), |()| {
dealloc(unaligned_bucket_array_ptr.cast::<u8>().as_ptr(), layout);
});
let data_block_layout = Self::data_block_layout(array_len);
let Some(data_blocks) =
NonNull::new(alloc(data_block_layout).cast::<DataBlock<K, V, BUCKET_LEN>>())
else {
panic!("Memory allocation failed: {data_block_layout:?}");
};
alloc_guard.forget();
#[cfg(feature = "loom")]
for i in 0..array_len {
buckets.add(i).write(Bucket::new());
}
Self {
buckets,
data_blocks,
array_len,
hash_offset: u8::try_from(u64::BITS).unwrap_or(64) - log2_array_len,
sample_size,
bucket_ptr_offset: bucket_array_ptr_offset,
linked_array,
num_cleared_buckets: AtomicUsize::new(0),
}
}
}
#[inline]
pub(crate) const fn len(&self) -> usize {
self.array_len
}
#[inline]
pub(crate) const fn num_slots(&self) -> usize {
self.array_len * BUCKET_LEN
}
#[allow(clippy::cast_possible_truncation)] #[inline]
pub(crate) const fn bucket_index(&self, hash: u64) -> usize {
(hash >> self.hash_offset) as usize
}
#[inline]
pub(crate) const fn minimum_capacity() -> usize {
BUCKET_LEN << 1
}
pub(crate) const fn need_enlarge(capacity: usize, num_entries: usize) -> bool {
num_entries > (capacity / 16) * 13
}
pub(crate) const fn need_shrink(capacity: usize, num_entries: usize) -> bool {
num_entries < capacity / 8
}
#[inline]
pub(crate) const fn optimal_capacity(
capacity: usize,
num_entries: usize,
minimum_capacity: usize,
maximum_capacity: usize,
) -> usize {
if capacity < minimum_capacity || Self::need_enlarge(capacity, num_entries) {
if capacity == maximum_capacity {
capacity
} else {
let mut new_capacity = minimum_capacity.next_power_of_two();
new_capacity = if capacity < new_capacity {
new_capacity
} else {
capacity
};
new_capacity = if maximum_capacity < new_capacity {
maximum_capacity
} else {
new_capacity
};
while new_capacity / 2 < num_entries {
if new_capacity >= maximum_capacity {
break;
}
new_capacity *= 2;
}
new_capacity
}
} else if Self::need_shrink(capacity, num_entries) {
let mut new_capacity = num_entries * 2;
new_capacity = if minimum_capacity < new_capacity {
new_capacity
} else {
minimum_capacity
};
new_capacity = if Self::minimum_capacity() < new_capacity {
new_capacity
} else {
Self::minimum_capacity()
};
new_capacity.next_power_of_two()
} else {
capacity
}
}
#[inline]
pub(crate) const fn rehashing_metadata(&self) -> &AtomicUsize {
&self.num_cleared_buckets
}
#[allow(clippy::cast_possible_truncation)] #[inline]
pub(crate) const fn initiate_sampling(&self, hash: u64) -> bool {
(hash as u8 & (self.sample_size - 1)) == 0
}
#[inline]
pub(crate) const fn small_sample_size(&self) -> usize {
(1 + self.sample_size.trailing_zeros()).next_power_of_two() as usize
}
#[inline]
pub(crate) const fn large_sample_size(&self) -> usize {
self.sample_size as usize
}
#[inline]
pub(crate) const fn full_sample_size(&self) -> usize {
if self.array_len < 128 {
self.array_len
} else {
128
}
}
#[inline]
pub(crate) const fn bucket(&self, index: usize) -> &Bucket<K, V, L, TYPE> {
debug_assert!(index < self.len());
unsafe { self.buckets.add(index).as_ref() }
}
#[inline]
pub(crate) const fn data_block(&self, index: usize) -> NonNull<DataBlock<K, V, BUCKET_LEN>> {
debug_assert!(index < self.len());
unsafe { self.data_blocks.add(index) }
}
#[inline]
pub(crate) const fn linked_array_var(&self) -> &AtomicRaw<BucketArray<K, V, L, TYPE>> {
&self.linked_array
}
#[inline]
pub(crate) fn has_linked_array(&self) -> bool {
self.linked_array.load(Acquire, fake_ref(self)) != RawPtr::null()
}
#[inline]
pub(crate) fn linked_array<'g>(
&self,
guard: &'g Guard,
) -> Option<&'g BucketArray<K, V, L, TYPE>> {
unsafe {
self.linked_array
.load(Acquire, guard)
.into_ptr()
.as_ref_unchecked()
}
}
#[inline]
const fn bucket_array_layout(array_len: usize) -> Layout {
let size_of_t = size_of::<Bucket<K, V, L, TYPE>>();
let allocation_size = (array_len + 1) * size_of_t;
unsafe { Layout::from_size_align_unchecked(allocation_size, 1) }
}
#[inline]
fn data_block_layout(array_len: usize) -> Layout {
unwrap_unchecked(
Layout::from_size_align(
size_of::<DataBlock<K, V, BUCKET_LEN>>() * array_len,
align_of::<[DataBlock<K, V, BUCKET_LEN>; 0]>(),
)
.ok(),
)
}
}
impl<K, V, L: LruList, const TYPE: char> Drop for BucketArray<K, V, L, TYPE> {
fn drop(&mut self) {
if let Some(bucket_array) = get_owned(self.linked_array.load(Relaxed, fake_ref(self))) {
unsafe {
bucket_array.drop_in_place();
}
}
let num_cleared_buckets = if TYPE == INDEX && needs_drop::<(K, V)>() {
0
} else {
self.num_cleared_buckets.load(Relaxed)
};
(num_cleared_buckets..self.array_len).for_each(|i| {
self.bucket(i).drop_entries(self.data_block(i));
});
#[cfg(feature = "loom")]
for i in 0..self.array_len {
drop(unsafe { self.buckets.add(i).read() });
}
unsafe {
dealloc(
self.buckets
.cast::<u8>()
.sub(self.bucket_ptr_offset as usize)
.as_ptr(),
Self::bucket_array_layout(self.array_len),
);
dealloc(
self.data_blocks.cast::<u8>().as_ptr(),
Self::data_block_layout(self.array_len),
);
}
}
}
unsafe impl<K: Send, V: Send, L: LruList, const TYPE: char> Send for BucketArray<K, V, L, TYPE> {}
unsafe impl<K: Send + Sync, V: Send + Sync, L: LruList, const TYPE: char> Sync
for BucketArray<K, V, L, TYPE>
{
}
impl<K: UnwindSafe, V: UnwindSafe, L: LruList, const TYPE: char> UnwindSafe
for BucketArray<K, V, L, TYPE>
{
}