use yo_common::{Addr, CACHE_LINE};
pub const SLOTS: usize = 7;
pub const EMPTY: u8 = 0;
const FLAG_OVERFLOW: u8 = 1;
const ONES: u64 = 0x0101_0101_0101_0101;
const LOW7: u64 = 0x7f7f_7f7f_7f7f_7f7f;
const LANES: u64 = 0x0080_8080_8080_8080;
#[repr(C, align(64))]
#[derive(Clone, Copy)]
pub struct Bucket {
tags: [u8; SLOTS],
flags: u8,
addrs: [[u8; 7]; SLOTS],
link: [u8; 7],
}
const _: () = {
assert!(size_of::<Bucket>() == CACHE_LINE);
assert!(align_of::<Bucket>() == CACHE_LINE);
};
impl Default for Bucket {
fn default() -> Bucket {
Bucket::EMPTY
}
}
impl Bucket {
pub const EMPTY: Bucket = Bucket {
tags: [EMPTY; SLOTS],
flags: 0,
addrs: [[0; 7]; SLOTS],
link: [0; 7],
};
#[inline(always)]
fn tag_word(&self) -> u64 {
let base: *const u8 = core::ptr::from_ref(self).cast();
unsafe { base.cast::<u64>().read_unaligned().to_le() }
}
#[inline(always)]
pub fn match_tag(&self, tag: u8) -> SlotMask {
let word = self.tag_word();
let x = word ^ (ONES.wrapping_mul(tag as u64));
let z = !((x & LOW7).wrapping_add(LOW7) | x | LOW7);
SlotMask(z & LANES)
}
#[inline(always)]
pub fn match_empty(&self) -> SlotMask {
self.match_tag(EMPTY)
}
#[inline(always)]
pub fn is_full(&self) -> bool {
self.match_empty().is_empty()
}
#[inline(always)]
pub fn tag(&self, i: usize) -> u8 {
self.tags[i]
}
#[inline(always)]
pub fn addr(&self, i: usize) -> Addr {
Addr::from_bits(read56(&self.addrs[i]))
}
#[inline(always)]
pub fn set(&mut self, i: usize, tag: u8, addr: Addr) {
assert_ne!(tag, EMPTY, "an occupied slot cannot carry the empty tag");
self.tags[i] = tag;
write56(&mut self.addrs[i], addr.to_bits());
}
#[inline(always)]
pub fn set_addr(&mut self, i: usize, addr: Addr) {
debug_assert_ne!(self.tags[i], EMPTY);
write56(&mut self.addrs[i], addr.to_bits());
}
#[inline(always)]
pub fn clear(&mut self, i: usize) {
self.tags[i] = EMPTY;
self.addrs[i] = [0; 7];
}
#[inline(always)]
pub fn link(&self) -> Option<u64> {
if self.flags & FLAG_OVERFLOW == 0 {
return None;
}
Some(read56(&self.link))
}
#[inline(always)]
pub fn has_overflow(&self) -> bool {
self.flags & FLAG_OVERFLOW != 0
}
#[inline]
pub fn set_link(&mut self, target: u64) {
write56(&mut self.link, target);
self.flags |= FLAG_OVERFLOW;
}
#[inline]
pub fn clear_link(&mut self) {
self.link = [0; 7];
self.flags &= !FLAG_OVERFLOW;
}
pub fn occupancy(&self) -> u32 {
self.tags.iter().filter(|&&t| t != EMPTY).count() as u32
}
}
impl core::fmt::Debug for Bucket {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Bucket")
.field("tags", &self.tags)
.field("occupancy", &self.occupancy())
.field("link", &self.link())
.finish()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SlotMask(u64);
impl SlotMask {
#[inline(always)]
pub const fn is_empty(self) -> bool {
self.0 == 0
}
#[inline(always)]
pub const fn first(self) -> Option<usize> {
if self.0 == 0 {
None
} else {
Some((self.0.trailing_zeros() / 8) as usize)
}
}
#[inline(always)]
pub const fn count(self) -> u32 {
self.0.count_ones()
}
}
impl Iterator for SlotMask {
type Item = usize;
#[inline(always)]
fn next(&mut self) -> Option<usize> {
if self.0 == 0 {
return None;
}
let i = (self.0.trailing_zeros() / 8) as usize;
self.0 &= self.0 - 1;
Some(i)
}
}
#[inline(always)]
fn read56(b: &[u8; 7]) -> u64 {
u64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], 0])
}
#[inline(always)]
fn write56(b: &mut [u8; 7], v: u64) {
let x = v.to_le_bytes();
b.copy_from_slice(&x[..7]);
}
#[cfg(test)]
mod tests {
use super::*;
use yo_common::Space;
#[test]
fn a_bucket_is_one_cache_line() {
assert_eq!(size_of::<Bucket>(), 64);
assert_eq!(align_of::<Bucket>(), 64);
}
#[test]
fn field_offsets_match_the_specification() {
let b = Bucket::EMPTY;
let base = (&b as *const Bucket).addr();
assert_eq!((&raw const b.tags).addr() - base, 0);
assert_eq!((&raw const b.flags).addr() - base, 7);
assert_eq!((&raw const b.addrs).addr() - base, 8);
assert_eq!((&raw const b.link).addr() - base, 57);
}
#[test]
fn an_empty_bucket_matches_nothing_and_is_all_free() {
let b = Bucket::EMPTY;
assert!(b.match_tag(1).is_empty());
assert!(b.match_tag(255).is_empty());
assert_eq!(b.match_empty().count(), SLOTS as u32);
assert!(!b.is_full());
}
#[test]
fn set_then_find() {
let mut b = Bucket::EMPTY;
let a = Addr::new(Space::Arena, 0x1234_5678);
b.set(3, 0xAB, a);
let m = b.match_tag(0xAB);
assert_eq!(m.count(), 1);
assert_eq!(m.first(), Some(3));
assert_eq!(b.addr(3), a);
assert_eq!(b.tag(3), 0xAB);
}
#[test]
fn every_slot_round_trips_every_space() {
for &space in Space::ALL {
for i in 0..SLOTS {
let mut b = Bucket::EMPTY;
let a = Addr::new(space, yo_common::MAX_OFFSET);
b.set(i, 0x5A, a);
assert_eq!(b.addr(i), a, "slot {i} space {space:?}");
assert_eq!(b.match_tag(0x5A).first(), Some(i));
}
}
}
#[test]
fn the_last_slot_does_not_bleed_into_the_link() {
let mut b = Bucket::EMPTY;
let a = Addr::new(Space::Arena, 0xABCD);
b.set(SLOTS - 1, 0x11, a);
b.set_link(yo_common::MAX_OFFSET);
assert_eq!(b.addr(SLOTS - 1), a);
assert_eq!(b.link(), Some(yo_common::MAX_OFFSET));
}
#[test]
fn the_link_does_not_bleed_into_the_last_slot() {
let mut b = Bucket::EMPTY;
b.set_link(u64::MAX >> 8);
let a = Addr::new(Space::Graph, 7);
b.set(SLOTS - 1, 0x22, a);
assert_eq!(b.link(), Some(u64::MAX >> 8));
assert_eq!(b.addr(SLOTS - 1), a);
}
#[test]
fn all_seven_slots_are_independent() {
let mut b = Bucket::EMPTY;
for i in 0..SLOTS {
b.set(
i,
(i as u8) + 1,
Addr::new(Space::Arena, (i as u64 + 1) * 16),
);
}
assert!(b.is_full());
for i in 0..SLOTS {
assert_eq!(b.tag(i), (i as u8) + 1);
assert_eq!(b.addr(i).offset(), (i as u64 + 1) * 16);
assert_eq!(b.match_tag((i as u8) + 1).first(), Some(i));
}
}
#[test]
fn duplicate_tags_all_report() {
let mut b = Bucket::EMPTY;
b.set(1, 0x77, Addr::new(Space::Arena, 16));
b.set(4, 0x77, Addr::new(Space::Arena, 32));
b.set(6, 0x77, Addr::new(Space::Arena, 48));
let m = b.match_tag(0x77);
assert_eq!(m.count(), 3);
assert_eq!(m.collect::<Vec<_>>(), vec![1, 4, 6]);
}
#[test]
fn the_flags_byte_is_never_a_match() {
let mut b = Bucket::EMPTY;
b.set_link(1); assert!(
b.match_tag(1).is_empty(),
"flags leaked into the tag search"
);
assert_eq!(b.match_empty().count(), SLOTS as u32);
let c = Bucket::EMPTY;
assert_eq!(c.match_empty().count(), SLOTS as u32);
}
#[test]
fn clear_frees_the_slot() {
let mut b = Bucket::EMPTY;
b.set(2, 0x99, Addr::new(Space::Arena, 64));
assert_eq!(b.match_empty().count(), 6);
b.clear(2);
assert!(b.match_tag(0x99).is_empty());
assert_eq!(b.match_empty().count(), 7);
assert_eq!(b.addr(2), Addr::NONE);
}
#[test]
fn links_attach_and_detach() {
let mut b = Bucket::EMPTY;
assert_eq!(b.link(), None);
assert!(!b.has_overflow());
b.set_link(4096);
assert!(b.has_overflow());
assert_eq!(b.link(), Some(4096));
b.clear_link();
assert_eq!(b.link(), None);
assert!(!b.has_overflow());
}
#[test]
#[should_panic(expected = "empty tag")]
fn setting_the_empty_tag_is_refused() {
let mut b = Bucket::EMPTY;
b.set(0, EMPTY, Addr::new(Space::Arena, 16));
}
#[test]
fn swar_agrees_with_a_plain_scan() {
for pattern in 0u32..128 {
let mut b = Bucket::EMPTY;
for i in 0..SLOTS {
if pattern & (1 << i) != 0 {
b.set(i, (i as u8) + 1, Addr::new(Space::Arena, 16));
}
}
const MIRI_TAGS: [u8; 14] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0x40, 0x7f, 0x80, 0xff];
let tags: Vec<u8> = if cfg!(miri) {
MIRI_TAGS.to_vec()
} else {
(0..=255u8).collect()
};
for tag in tags {
let want: Vec<usize> = (0..SLOTS).filter(|&i| b.tags[i] == tag).collect();
let got: Vec<usize> = b.match_tag(tag).collect();
assert_eq!(got, want, "pattern {pattern:#b} tag {tag}");
}
}
}
}