use std::alloc::{Layout, alloc, dealloc, handle_alloc_error};
use std::ptr::NonNull;
#[derive(Clone, Copy)]
#[repr(C)]
struct Entry {
key: u128,
val: u64,
ext: u64,
}
const _: () = assert!(std::mem::size_of::<Entry>() == 32);
const EMPTY_KEY: u128 = 0;
struct Slots {
ptr: NonNull<Entry>,
cap: usize,
}
impl Slots {
const HUGE_PAGE: usize = 2 * 1024 * 1024;
fn new_zeroed(cap: usize) -> Self {
let layout = Self::layout(cap);
let raw = unsafe { alloc(layout) };
let Some(ptr) = NonNull::new(raw as *mut Entry) else {
handle_alloc_error(layout)
};
super::madvise_hugepage(raw, layout.size());
unsafe { std::ptr::write_bytes(raw, 0, layout.size()) };
Self { ptr, cap }
}
fn layout(cap: usize) -> Layout {
let size = cap * std::mem::size_of::<Entry>();
let align = Self::HUGE_PAGE.min(size.next_power_of_two()).max(64);
Layout::from_size_align(size, align).expect("table layout overflow")
}
#[inline(always)]
unsafe fn get(&self, idx: usize) -> &Entry {
debug_assert!(idx < self.cap);
unsafe { &*self.ptr.as_ptr().add(idx) }
}
#[inline(always)]
unsafe fn get_mut(&mut self, idx: usize) -> &mut Entry {
debug_assert!(idx < self.cap);
unsafe { &mut *self.ptr.as_ptr().add(idx) }
}
}
impl Drop for Slots {
fn drop(&mut self) {
unsafe { dealloc(self.ptr.as_ptr() as *mut u8, Self::layout(self.cap)) };
}
}
unsafe impl Send for Slots {}
unsafe impl Sync for Slots {}
#[inline(always)]
fn prefetch_line<const L1: bool>(p: *const Entry) {
#[cfg(target_arch = "x86_64")]
unsafe {
use core::arch::x86_64::{_MM_HINT_T0, _MM_HINT_T1, _mm_prefetch};
if L1 {
_mm_prefetch(p as *const i8, _MM_HINT_T0);
} else {
_mm_prefetch(p as *const i8, _MM_HINT_T1);
}
}
#[cfg(target_arch = "aarch64")]
unsafe {
if L1 {
core::arch::asm!(
"prfm pldl1keep, [{p}]",
p = in(reg) p,
options(nostack, preserves_flags, readonly)
);
} else {
core::arch::asm!(
"prfm pldl2keep, [{p}]",
p = in(reg) p,
options(nostack, preserves_flags, readonly)
);
}
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
let _ = p;
}
pub(crate) struct ShortPretokenCache {
slots: Slots,
mask: usize,
len: usize,
}
impl ShortPretokenCache {
fn with_pow2_capacity(cap: usize) -> Self {
debug_assert!(cap.is_power_of_two() && cap >= 2);
Self {
slots: Slots::new_zeroed(cap),
mask: cap - 1,
len: 0,
}
}
pub(crate) fn with_at_least(n: usize, min_slots: usize) -> Self {
let mut cap = min_slots.max(1 << 16).next_power_of_two();
while (n + 1) * 4 > cap * 3 {
cap *= 2;
}
Self::with_pow2_capacity(cap)
}
#[inline(always)]
fn pair_ptr(&self, h: u64) -> *const Entry {
unsafe { self.slots.ptr.as_ptr().add((h as usize) & self.mask & !1) }
}
#[inline(always)]
pub(crate) fn prefetch_l2(&self, h: u64) {
prefetch_line::<false>(self.pair_ptr(h));
}
pub(crate) fn probe_view(&self) -> ProbeView {
ProbeView {
base: self.slots.ptr.as_ptr(),
pair_mask: self.mask & !1,
}
}
pub(crate) fn get_or_slot(&self, key: u128, h: u64) -> Result<(u64, u64), usize> {
debug_assert_ne!(key, EMPTY_KEY);
let mut idx = (h as usize) & self.mask & !1;
loop {
let e0 = unsafe { self.slots.get(idx) };
let e1 = unsafe { self.slots.get(idx + 1) };
if e0.key == key {
return Ok((e0.val, e0.ext));
}
if e1.key == key {
return Ok((e1.val, e1.ext));
}
if e0.key == EMPTY_KEY {
return Err(idx);
}
if e1.key == EMPTY_KEY {
return Err(idx + 1);
}
idx = (idx + 2) & self.mask;
}
}
fn first_empty(&self, h: u64) -> usize {
let mut idx = (h as usize) & self.mask & !1;
loop {
unsafe {
if self.slots.get(idx).key == EMPTY_KEY {
return idx;
}
if self.slots.get(idx + 1).key == EMPTY_KEY {
return idx + 1;
}
}
idx = (idx + 2) & self.mask;
}
}
pub(crate) fn insert(&mut self, key: u128, h: u64, val: u64, ext: u64) {
debug_assert_ne!(key, EMPTY_KEY);
if (self.len + 1) * 4 > self.slots.cap * 3 {
self.grow();
}
let idx = self.first_empty(h);
unsafe { *self.slots.get_mut(idx) = Entry { key, val, ext } };
self.len += 1;
}
pub(crate) fn insert_at(&mut self, slot: usize, key: u128, h: u64, val: u64, ext: u64) {
debug_assert_ne!(key, EMPTY_KEY);
let mut slot = slot;
if (self.len + 1) * 4 > self.slots.cap * 3 {
self.grow();
slot = self.first_empty(h);
}
debug_assert_eq!(slot, self.first_empty(h));
unsafe { *self.slots.get_mut(slot) = Entry { key, val, ext } };
self.len += 1;
}
pub(crate) fn replace(&mut self, key: u128, h: u64, val: u64, ext: u64) {
debug_assert_ne!(key, EMPTY_KEY);
let mut idx = (h as usize) & self.mask & !1;
loop {
let (k0, k1) = unsafe { (self.slots.get(idx).key, self.slots.get(idx + 1).key) };
if k0 == key {
unsafe { *self.slots.get_mut(idx) = Entry { key, val, ext } };
return;
}
if k1 == key {
unsafe { *self.slots.get_mut(idx + 1) = Entry { key, val, ext } };
return;
}
if k0 == EMPTY_KEY || k1 == EMPTY_KEY {
self.insert(key, h, val, ext);
return;
}
idx = (idx + 2) & self.mask;
}
}
#[cold]
fn grow(&mut self) {
let new_cap = self.slots.cap * 2;
let old = std::mem::replace(&mut self.slots, Slots::new_zeroed(new_cap));
self.mask = new_cap - 1;
for i in 0..old.cap {
let e = *unsafe { old.get(i) };
if e.key == EMPTY_KEY {
continue;
}
let idx = self.first_empty(crate::pretokenize::pretoken_key_hash(e.key));
unsafe { *self.slots.get_mut(idx) = e };
}
}
pub(crate) fn len(&self) -> usize {
self.len
}
pub(crate) fn capacity(&self) -> usize {
self.slots.cap
}
}
#[derive(Clone, Copy)]
pub(crate) struct ProbeView {
base: *const Entry,
pair_mask: usize,
}
impl ProbeView {
#[inline(always)]
fn pair_ptr(&self, h: u64) -> *const Entry {
unsafe { self.base.add((h as usize) & self.pair_mask) }
}
#[inline(always)]
pub(crate) fn prefetch(&self, h: u64) {
prefetch_line::<true>(self.pair_ptr(h));
}
#[inline(always)]
pub(crate) fn probe_pair(&self, key: u128, h: u64) -> (u64, u64, bool) {
let p = self.pair_ptr(h);
let (e0, e1) = unsafe { (&*p, &*p.add(1)) };
let m0 = e0.key == key;
let m1 = e1.key == key;
#[cfg(target_arch = "aarch64")]
let (val, ext) = {
let (mut val, mut ext) = (e0.val, e0.ext);
unsafe {
core::arch::asm!(
"cmp {m}, #0",
"csel {val}, {val}, {v1}, ne",
"csel {ext}, {ext}, {x1}, ne",
m = in(reg) m0 as u64,
val = inout(reg) val,
ext = inout(reg) ext,
v1 = in(reg) e1.val,
x1 = in(reg) e1.ext,
options(pure, nomem, nostack),
);
}
(val, ext)
};
#[cfg(target_arch = "x86_64")]
let (val, ext) = {
let (mut val, mut ext) = (e1.val, e1.ext);
unsafe {
core::arch::asm!(
"test {m}, {m}",
"cmovne {val}, {v0}",
"cmovne {ext}, {x0}",
m = in(reg) m0 as u64,
val = inout(reg) val,
ext = inout(reg) ext,
v0 = in(reg) e0.val,
x0 = in(reg) e0.ext,
options(pure, nomem, nostack),
);
}
(val, ext)
};
#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
let (val, ext) = {
let sel = (m0 as u64).wrapping_neg();
(
(e0.val & sel) | (e1.val & !sel),
(e0.ext & sel) | (e1.ext & !sel),
)
};
(val, ext, m0 | m1)
}
}