use crate::bucket::{Bucket, SLOTS};
use crate::scan::{Cursor, PREFIX_BITS};
use yo_common::{Addr, tag_of};
pub const SEGMENT_BUCKETS: usize = 64;
pub const MAX_CHAIN: usize = 2;
pub(crate) const DIR_BITS: u32 = 56;
const MAX_DEPTH: u8 = 48;
struct Segment {
buckets: Vec<Bucket>,
overflow: Vec<Bucket>,
local_depth: u8,
}
impl Segment {
fn new(local_depth: u8) -> Segment {
Segment {
buckets: vec![Bucket::EMPTY; SEGMENT_BUCKETS],
overflow: Vec::new(),
local_depth,
}
}
}
pub trait Keys {
fn hash_at(&self, addr: Addr) -> u64;
fn eq_at(&self, addr: Addr, key: &[u8]) -> bool;
}
#[derive(Debug)]
pub struct Index {
dir: Vec<u32>,
segs: Vec<Segment>,
global_depth: u8,
len: usize,
splits: u64,
doublings: u64,
}
impl core::fmt::Debug for Segment {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Segment")
.field("local_depth", &self.local_depth)
.field("overflow", &self.overflow.len())
.finish()
}
}
impl Index {
pub fn new() -> Index {
Index {
dir: vec![0],
segs: vec![Segment::new(0)],
global_depth: 0,
len: 0,
splits: 0,
doublings: 0,
}
}
#[inline]
pub fn len(&self) -> usize {
self.len
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn segment_count(&self) -> usize {
self.segs.len()
}
pub fn global_depth(&self) -> u8 {
self.global_depth
}
pub fn splits(&self) -> u64 {
self.splits
}
pub fn doublings(&self) -> u64 {
self.doublings
}
pub fn memory_bytes(&self) -> usize {
self.dir.len() * size_of::<u32>()
+ self
.segs
.iter()
.map(|s| (s.buckets.len() + s.overflow.len()) * size_of::<Bucket>())
.sum::<usize>()
}
#[inline(always)]
fn dir_index(&self, hash: u64) -> usize {
let d = self.global_depth as u32;
if d == 0 {
return 0;
}
((hash >> (DIR_BITS - d)) & ((1u64 << d) - 1)) as usize
}
#[inline(always)]
fn bucket_index(hash: u64) -> usize {
(hash as usize) & (SEGMENT_BUCKETS - 1)
}
#[inline(always)]
pub fn prefetch(&self, hash: u64) {
let seg = &self.segs[self.dir[self.dir_index(hash)] as usize];
yo_common::prefetch(&seg.buckets[Self::bucket_index(hash)]);
}
#[inline]
pub fn get<K: Keys>(&self, hash: u64, key: &[u8], keys: &K) -> Option<Addr> {
let tag = tag_of(hash);
let seg = &self.segs[self.dir[self.dir_index(hash)] as usize];
let mut b = &seg.buckets[Self::bucket_index(hash)];
loop {
for i in b.match_tag(tag) {
let addr = b.addr(i);
if keys.eq_at(addr, key) {
return Some(addr);
}
}
{
let next = b.link()?;
b = &seg.overflow[(next - 1) as usize]
}
}
}
#[inline]
pub fn contains<K: Keys>(&self, hash: u64, key: &[u8], keys: &K) -> bool {
self.get(hash, key, keys).is_some()
}
pub fn insert<K: Keys>(&mut self, hash: u64, key: &[u8], addr: Addr, keys: &K) -> Option<Addr> {
debug_assert!(addr.is_some(), "the index cannot store the absent address");
let tag = tag_of(hash);
loop {
let seg_idx = self.dir[self.dir_index(hash)] as usize;
let bucket_idx = Self::bucket_index(hash);
let mut free: Option<(usize, usize)> = None;
let mut chain_len = 0usize;
let mut cursor: Option<usize> = None;
loop {
let seg = &self.segs[seg_idx];
let b = match cursor {
None => &seg.buckets[bucket_idx],
Some(o) => &seg.overflow[o],
};
for i in b.match_tag(tag) {
if keys.eq_at(b.addr(i), key) {
let old = b.addr(i);
let seg = &mut self.segs[seg_idx];
let b = match cursor {
None => &mut seg.buckets[bucket_idx],
Some(o) => &mut seg.overflow[o],
};
b.set_addr(i, addr);
return Some(old);
}
}
if free.is_none()
&& let Some(i) = b.match_empty().first()
{
free = Some((cursor.unwrap_or(usize::MAX), i));
}
match b.link() {
Some(next) => {
cursor = Some((next - 1) as usize);
chain_len += 1;
}
None => break,
}
}
if let Some((where_, slot)) = free {
let seg = &mut self.segs[seg_idx];
let b = if where_ == usize::MAX {
&mut seg.buckets[bucket_idx]
} else {
&mut seg.overflow[where_]
};
b.set(slot, tag, addr);
self.len += 1;
return None;
}
if chain_len < MAX_CHAIN || self.segs[seg_idx].local_depth >= MAX_DEPTH {
self.extend_chain(seg_idx, bucket_idx, cursor, tag, addr);
self.len += 1;
return None;
}
self.split(seg_idx, keys);
}
}
fn extend_chain(
&mut self,
seg_idx: usize,
bucket_idx: usize,
tail: Option<usize>,
tag: u8,
addr: Addr,
) {
let seg = &mut self.segs[seg_idx];
let mut fresh = Bucket::EMPTY;
fresh.set(0, tag, addr);
yo_alloc::allow(|| seg.overflow.push(fresh));
let new_idx = seg.overflow.len() - 1;
let link = (new_idx + 1) as u64;
match tail {
None => seg.buckets[bucket_idx].set_link(link),
Some(o) => seg.overflow[o].set_link(link),
}
}
pub fn remove<K: Keys>(&mut self, hash: u64, key: &[u8], keys: &K) -> Option<Addr> {
let tag = tag_of(hash);
let seg_idx = self.dir[self.dir_index(hash)] as usize;
let bucket_idx = Self::bucket_index(hash);
let mut cursor: Option<usize> = None;
loop {
let seg = &self.segs[seg_idx];
let b = match cursor {
None => &seg.buckets[bucket_idx],
Some(o) => &seg.overflow[o],
};
let mut hit = None;
for i in b.match_tag(tag) {
if keys.eq_at(b.addr(i), key) {
hit = Some((i, b.addr(i)));
break;
}
}
if let Some((i, addr)) = hit {
let seg = &mut self.segs[seg_idx];
let b = match cursor {
None => &mut seg.buckets[bucket_idx],
Some(o) => &mut seg.overflow[o],
};
b.clear(i);
self.len -= 1;
return Some(addr);
}
let next = {
let seg = &self.segs[seg_idx];
let b = match cursor {
None => &seg.buckets[bucket_idx],
Some(o) => &seg.overflow[o],
};
b.link()
};
{
let n = next?;
cursor = Some((n - 1) as usize)
}
}
}
fn split<K: Keys>(&mut self, seg_idx: usize, keys: &K) {
let ld = self.segs[seg_idx].local_depth;
if ld == self.global_depth {
self.double_directory();
}
let gd = self.global_depth;
debug_assert!(ld < gd);
self.segs[seg_idx].local_depth = ld + 1;
yo_alloc::allow(|| self.segs.push(Segment::new(ld + 1)));
let new_idx = self.segs.len() - 1;
self.splits += 1;
let shift = (gd - 1 - ld) as u32;
for i in 0..self.dir.len() {
if self.dir[i] as usize == seg_idx && ((i >> shift) & 1) == 1 {
self.dir[i] = new_idx as u32;
}
}
let mut moving: Vec<(u64, Addr)> = Vec::new();
yo_alloc::allow(|| {
let seg = &mut self.segs[seg_idx];
let mut visit = |b: &mut Bucket| {
for i in 0..SLOTS {
if b.tag(i) == crate::bucket::EMPTY {
continue;
}
let addr = b.addr(i);
let h = keys.hash_at(addr);
if ((h >> (DIR_BITS - gd as u32)) & ((1u64 << gd) - 1)) >> shift & 1 == 1 {
moving.push((h, addr));
b.clear(i);
}
}
};
for b in seg.buckets.iter_mut() {
visit(b);
}
for b in seg.overflow.iter_mut() {
visit(b);
}
});
for (h, addr) in moving {
self.place_raw(new_idx, h, addr);
}
}
fn place_raw(&mut self, seg_idx: usize, hash: u64, addr: Addr) {
let tag = tag_of(hash);
let bucket_idx = Self::bucket_index(hash);
let mut cursor: Option<usize> = None;
loop {
let seg = &mut self.segs[seg_idx];
let b = match cursor {
None => &mut seg.buckets[bucket_idx],
Some(o) => &mut seg.overflow[o],
};
if let Some(i) = b.match_empty().first() {
b.set(i, tag, addr);
return;
}
match b.link() {
Some(n) => cursor = Some((n - 1) as usize),
None => {
self.extend_chain(seg_idx, bucket_idx, cursor, tag, addr);
return;
}
}
}
}
fn double_directory(&mut self) {
assert!(
self.global_depth < MAX_DEPTH,
"the directory has run out of hash bits"
);
yo_alloc::allow(|| {
let mut next = Vec::with_capacity(self.dir.len() * 2);
for &s in &self.dir {
next.push(s);
next.push(s);
}
self.dir = next;
});
self.global_depth += 1;
self.doublings += 1;
}
pub fn addresses(&self) -> impl Iterator<Item = Addr> + '_ {
self.segs
.iter()
.enumerate()
.flat_map(move |(si, seg)| {
let _ = si;
seg.buckets.iter().chain(seg.overflow.iter())
})
.flat_map(|b| {
(0..SLOTS).filter_map(move |i| {
if b.tag(i) == crate::bucket::EMPTY {
None
} else {
Some(b.addr(i))
}
})
})
}
pub fn sample(&self, r: u64, mut out: impl FnMut(Addr) -> bool) {
let seg = &self.segs[(r >> 32) as usize % self.segs.len()];
let first = (r as usize) % SEGMENT_BUCKETS;
for step in 0..SEGMENT_BUCKETS {
let mut b = &seg.buckets[(first + step) % SEGMENT_BUCKETS];
loop {
for i in 0..SLOTS {
if b.tag(i) != crate::bucket::EMPTY && !out(b.addr(i)) {
return;
}
}
match b.link() {
Some(n) => b = &seg.overflow[(n - 1) as usize],
None => break,
}
}
}
}
pub fn scan(&self, from: Cursor, mut out: impl FnMut(Addr)) -> Cursor {
let g = u32::from(self.global_depth);
let prefix = from.prefix();
let bucket = from.bucket();
let dir_idx = (prefix >> (PREFIX_BITS - g)) as usize;
let seg = &self.segs[self.dir[dir_idx] as usize];
let mut b = &seg.buckets[bucket];
loop {
for i in 0..SLOTS {
if b.tag(i) != crate::bucket::EMPTY {
out(b.addr(i));
}
}
match b.link() {
Some(n) => b = &seg.overflow[(n - 1) as usize],
None => break,
}
}
if bucket + 1 < SEGMENT_BUCKETS {
return Cursor::at(prefix, bucket + 1);
}
let span = 1u64 << (PREFIX_BITS - u32::from(seg.local_depth));
Cursor::at((prefix & !(span - 1)) + span, 0)
}
pub fn relocate<K: Keys>(&mut self, hash: u64, key: &[u8], to: Addr, keys: &K) -> bool {
let tag = tag_of(hash);
let seg_idx = self.dir[self.dir_index(hash)] as usize;
let bucket_idx = Self::bucket_index(hash);
let mut cursor: Option<usize> = None;
loop {
let found = {
let seg = &self.segs[seg_idx];
let b = match cursor {
None => &seg.buckets[bucket_idx],
Some(o) => &seg.overflow[o],
};
let mut hit = None;
for i in b.match_tag(tag) {
if keys.eq_at(b.addr(i), key) {
hit = Some(i);
break;
}
}
(hit, b.link())
};
if let (Some(i), _) = found {
let seg = &mut self.segs[seg_idx];
let b = match cursor {
None => &mut seg.buckets[bucket_idx],
Some(o) => &mut seg.overflow[o],
};
b.set_addr(i, to);
return true;
}
match found.1 {
Some(n) => cursor = Some((n - 1) as usize),
None => return false,
}
}
}
}
impl Default for Index {
fn default() -> Index {
Index::new()
}
}