use crate::swar::load_u64;
pub const MAX_HASHED_KEYS: usize = 255;
const SLOT_CAP: usize = 256;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[repr(u8)]
pub enum HashKind {
Empty,
SingleElement,
Mod4,
XorMod4,
MinusMod4,
UniqueIndexTwo,
UniqueIndex,
UniqueIndexSized,
FrontHash2,
FrontHash4,
FrontHash8,
UniquePerLength,
FullFlat,
Linear,
}
#[derive(Clone, Copy)]
pub struct KeyMap {
pub kind: HashKind,
pub n: u32,
pub seed: u64,
pub unique_index: u32,
pub min_len: u32,
pub max_len: u32,
pub mask: u32,
pub table: [u8; SLOT_CAP],
pub per_len: [u8; SLOT_CAP],
}
#[inline(always)]
pub(crate) const fn bitmix(h: u64, seed: u64) -> u64 {
let h = h.wrapping_mul(seed);
h ^ h.rotate_right(49)
}
#[inline(always)]
pub(crate) const fn rich_bitmix(h: u64, seed: u64) -> u64 {
let mut h = h;
h ^= h >> 23;
h = h.wrapping_mul(0x2127_599b_f432_5c37);
h ^= seed;
h = h.wrapping_mul(0x8803_55f2_1e6d_1965);
h ^= h >> 47;
h
}
#[inline(always)]
pub(crate) const fn to_u64_below_8(data: &[u8], n: usize) -> u64 {
let mut v: u64 = 0;
let mut i = 0;
while i < n {
v |= (data[i] as u64) << (8 * i);
i += 1;
}
v
}
#[inline(always)]
pub(crate) const fn to_u64_at(data: &[u8], off: usize) -> u64 {
(data[off] as u64)
| ((data[off + 1] as u64) << 8)
| ((data[off + 2] as u64) << 16)
| ((data[off + 3] as u64) << 24)
| ((data[off + 4] as u64) << 32)
| ((data[off + 5] as u64) << 40)
| ((data[off + 6] as u64) << 48)
| ((data[off + 7] as u64) << 56)
}
const DIGEST_BASIS: u64 = 0x9E37_79B9_7F4A_7C15;
const fn key_digest(data: &[u8], n: usize) -> u64 {
if n < 8 {
return to_u64_below_8(data, n);
}
let mut h = DIGEST_BASIS;
let mut i = 0;
while i + 8 <= n {
h = bitmix(to_u64_at(data, i), h);
i += 8;
}
rich_bitmix(to_u64_at(data, n - 8), h)
}
pub(crate) const fn full_hash(data: &[u8], n: usize, seed: u64) -> u64 {
bitmix(key_digest(data, n), seed)
}
const fn seed_at(i: u64) -> u64 {
let mut z = (i.wrapping_add(1)).wrapping_mul(0x9E37_79B9_7F4A_7C15);
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
const SEED_ATTEMPTS: u64 = 4096;
const MAX_SEARCHED_KEYS: usize = 72;
const fn slots_for(n: usize) -> usize {
if n <= 1 {
return 1;
}
let sq = n * n;
let mut s = 1usize;
while s < sq {
s *= 2;
}
let s = s / 2;
if s > SLOT_CAP { SLOT_CAP } else { s }
}
impl KeyMap {
pub const fn build(keys: &[&str]) -> KeyMap {
let n = keys.len();
let mut m = KeyMap {
kind: HashKind::Empty,
n: n as u32,
seed: 0,
unique_index: 0,
min_len: 0,
max_len: 0,
mask: 0,
table: [0u8; SLOT_CAP],
per_len: [0u8; SLOT_CAP],
};
if n == 0 {
return m;
}
let mut i = 0;
while i < n {
let mut j = i + 1;
while j < n {
if const_str_eq(keys[i], keys[j]) {
panic!("structio: a declaration named the same key or variant twice");
}
j += 1;
}
i += 1;
}
let mut i = 0;
while i < n {
let b = keys[i].as_bytes();
let mut j = 0;
while j < b.len() {
if b[j] == b'"' || b[j] == b'\\' || b[j] < 0x20 {
panic!("structio: object key contains a character that JSON must escape");
}
j += 1;
}
i += 1;
}
let mut min_len = usize::MAX;
let mut max_len = 0usize;
let mut i = 0;
while i < n {
let l = keys[i].len();
if l < min_len {
min_len = l;
}
if l > max_len {
max_len = l;
}
i += 1;
}
m.min_len = min_len as u32;
m.max_len = max_len as u32;
if n > MAX_HASHED_KEYS {
m.kind = HashKind::Linear;
return m;
}
if n == 1 {
m.kind = HashKind::SingleElement;
return m;
}
m.mask = (slots_for(n) - 1) as u32;
if (n == 3 || n == 4)
&& min_len > 0
&& let Some(kind) = try_mod4(keys)
{
m.kind = kind;
m.seed = keys[0].as_bytes()[0] as u64;
return m;
}
if let Some(uidx) = find_unique_index(keys, min_len) {
m.unique_index = uidx as u32;
if n == 2 {
m.kind = HashKind::UniqueIndexTwo;
m.seed = keys[0].as_bytes()[uidx] as u64;
return m;
}
m.kind = HashKind::UniqueIndex;
m.table = [n as u8; SLOT_CAP];
let mut i = 0;
while i < n {
m.table[keys[i].as_bytes()[uidx] as usize] = i as u8;
i += 1;
}
return m;
}
let slots = slots_for(n);
let empty_per_len = [0u8; SLOT_CAP];
let mut width = 2usize;
while width <= 8 {
let mode = match width {
2 => SearchMode::Front2,
4 => SearchMode::Front4,
_ => SearchMode::Front8,
};
if min_len >= width
&& front_bytes_distinct(keys, width)
&& let Some((seed, table)) = search_seed(keys, slots, mode, 0, &empty_per_len)
{
m.kind = match width {
2 => HashKind::FrontHash2,
4 => HashKind::FrontHash4,
_ => HashKind::FrontHash8,
};
m.seed = seed;
m.table = table;
return m;
}
width *= 2;
}
if let Some(uidx) = find_sized_unique_index(keys)
&& let Some((seed, table)) =
search_seed(keys, slots, SearchMode::Sized, uidx, &empty_per_len)
{
m.kind = HashKind::UniqueIndexSized;
m.unique_index = uidx as u32;
m.seed = seed;
m.table = table;
return m;
}
if let Some(per_len) = unique_per_length(keys, min_len, max_len)
&& let Some((seed, table)) =
search_seed(keys, slots, SearchMode::PerLength, 0, &per_len)
{
m.kind = HashKind::UniquePerLength;
m.seed = seed;
m.table = table;
m.per_len = per_len;
return m;
}
if n <= MAX_SEARCHED_KEYS
&& let Some((seed, table)) =
search_seed(keys, slots, SearchMode::Full, 0, &empty_per_len)
{
m.kind = HashKind::FullFlat;
m.seed = seed;
m.table = table;
return m;
}
m.kind = HashKind::Linear;
m
}
}
const fn const_str_eq(a: &str, b: &str) -> bool {
let (a, b) = (a.as_bytes(), b.as_bytes());
if a.len() != b.len() {
return false;
}
let mut i = 0;
while i < a.len() {
if a[i] != b[i] {
return false;
}
i += 1;
}
true
}
const fn try_mod4(keys: &[&str]) -> Option<HashKind> {
let n = keys.len();
let mut ok = true;
let mut i = 0;
while i < n {
if keys[i].as_bytes()[0] % 4 != i as u8 {
ok = false;
}
i += 1;
}
if ok {
return Some(HashKind::Mod4);
}
let c0 = keys[0].as_bytes()[0];
ok = true;
i = 0;
while i < n {
if (keys[i].as_bytes()[0] ^ c0) % 4 != i as u8 {
ok = false;
}
i += 1;
}
if ok {
return Some(HashKind::XorMod4);
}
ok = true;
i = 0;
while i < n {
if keys[i].as_bytes()[0].wrapping_sub(c0) % 4 != i as u8 {
ok = false;
}
i += 1;
}
if ok {
return Some(HashKind::MinusMod4);
}
None
}
const fn find_unique_index(keys: &[&str], min_len: usize) -> Option<usize> {
if min_len == 0 {
return None;
}
let n = keys.len();
let mut col = 0usize;
while col < min_len {
let mut seen = [false; 256];
let mut distinct = true;
let mut i = 0;
while i < n {
let c = keys[i].as_bytes()[col] as usize;
if seen[c] {
distinct = false;
break;
}
seen[c] = true;
i += 1;
}
if distinct {
return Some(col);
}
col += 1;
}
None
}
const fn find_sized_unique_index(keys: &[&str]) -> Option<usize> {
let n = keys.len();
let mut min_len = usize::MAX;
let mut i = 0;
while i < n {
if keys[i].len() < min_len {
min_len = keys[i].len();
}
i += 1;
}
if min_len == 0 {
return None;
}
let mut col = 0usize;
while col < min_len {
let mut distinct = true;
let mut i = 0;
'outer: while i < n {
let mut j = i + 1;
while j < n {
if keys[i].len() == keys[j].len()
&& keys[i].as_bytes()[col] == keys[j].as_bytes()[col]
{
distinct = false;
break 'outer;
}
j += 1;
}
i += 1;
}
if distinct {
return Some(col);
}
col += 1;
}
None
}
const fn front_bytes_distinct(keys: &[&str], width: usize) -> bool {
let n = keys.len();
let mut i = 0;
while i < n {
let a = to_u64_below_8(keys[i].as_bytes(), width);
let mut j = i + 1;
while j < n {
if a == to_u64_below_8(keys[j].as_bytes(), width) {
return false;
}
j += 1;
}
i += 1;
}
true
}
const fn unique_per_length(
keys: &[&str],
min_len: usize,
max_len: usize,
) -> Option<[u8; SLOT_CAP]> {
if min_len == 0 || max_len >= SLOT_CAP {
return None;
}
let n = keys.len();
let mut out = [255u8; SLOT_CAP];
let mut len = min_len;
while len <= max_len {
let mut any = false;
let mut i = 0;
while i < n {
if keys[i].len() == len {
any = true;
break;
}
i += 1;
}
if !any {
len += 1;
continue;
}
let mut found = false;
let mut col = 0usize;
while col < len {
let mut seen = [false; 256];
let mut distinct = true;
let mut i = 0;
while i < n {
if keys[i].len() == len {
let c = keys[i].as_bytes()[col] as usize;
if seen[c] {
distinct = false;
break;
}
seen[c] = true;
}
i += 1;
}
if distinct {
out[len] = col as u8;
found = true;
break;
}
col += 1;
}
if !found {
return None;
}
len += 1;
}
Some(out)
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum SearchMode {
Front2,
Front4,
Front8,
Sized,
PerLength,
Full,
}
const fn search_preimage(
key: &[u8],
mode: SearchMode,
uidx: usize,
per_len: &[u8; SLOT_CAP],
) -> u64 {
match mode {
SearchMode::Front2 => to_u64_below_8(key, 2),
SearchMode::Front4 => to_u64_below_8(key, 4),
SearchMode::Front8 => to_u64_below_8(key, 8),
SearchMode::Sized => (key[uidx] as u64) | ((key.len() as u64) << 8),
SearchMode::PerLength => {
let col = per_len[key.len()] as usize;
(key[col] as u64) | ((key.len() as u64) << 8)
}
SearchMode::Full => key_digest(key, key.len()),
}
}
const fn mix_preimage(pre: u64, seed: u64, mode: SearchMode) -> u64 {
match mode {
SearchMode::Front8 => rich_bitmix(pre, seed),
SearchMode::Front2
| SearchMode::Front4
| SearchMode::Sized
| SearchMode::PerLength
| SearchMode::Full => bitmix(pre, seed),
}
}
const fn search_seed(
keys: &[&str],
slots: usize,
mode: SearchMode,
uidx: usize,
per_len: &[u8; SLOT_CAP],
) -> Option<(u64, [u8; SLOT_CAP])> {
let n = keys.len();
let mask = (slots - 1) as u64;
let mut pre = [0u64; MAX_HASHED_KEYS];
let mut i = 0;
while i < n {
pre[i] = search_preimage(keys[i].as_bytes(), mode, uidx, per_len);
i += 1;
}
let mut attempt = 0u64;
while attempt < SEED_ATTEMPTS {
let seed = seed_at(attempt);
if seed == 0 {
attempt += 1;
continue;
}
let mut table = [n as u8; SLOT_CAP];
let mut ok = true;
let mut i = 0;
while i < n {
let h = mix_preimage(pre[i], seed, mode) & mask;
if table[h as usize] != n as u8 {
ok = false;
break;
}
table[h as usize] = i as u8;
i += 1;
}
if ok {
return Some((seed, table));
}
attempt += 1;
}
None
}
#[inline(always)]
pub(crate) fn find_quote(buf: &[u8], from: usize) -> Option<usize> {
const ONES: u64 = 0x0101_0101_0101_0101;
const HIGH: u64 = 0x8080_8080_8080_8080;
const QUOTES: u64 = 0x2222_2222_2222_2222;
let n = buf.len();
let mut i = from;
while i + 8 <= n {
let chunk = u64::from_le(unsafe { (buf.as_ptr().add(i) as *const u64).read_unaligned() });
let x = chunk ^ QUOTES;
let m = x.wrapping_sub(ONES) & !x & HIGH;
if m != 0 {
return Some(i + (m.trailing_zeros() >> 3) as usize);
}
i += 8;
}
while i < n {
if buf[i] == b'"' {
return Some(i);
}
i += 1;
}
None
}
impl KeyMap {
#[inline(always)]
pub fn lookup(&self, keys: &[&'static str], buf: &[u8]) -> usize {
let n = self.n as usize;
match self.kind {
HashKind::Empty => n,
HashKind::SingleElement => 0,
HashKind::Mod4 => {
if buf.is_empty() {
return n;
}
(buf[0] & 3) as usize
}
HashKind::XorMod4 => {
if buf.is_empty() {
return n;
}
((buf[0] ^ self.seed as u8) & 3) as usize
}
HashKind::MinusMod4 => {
if buf.is_empty() {
return n;
}
(buf[0].wrapping_sub(self.seed as u8) & 3) as usize
}
HashKind::UniqueIndexTwo => {
let u = self.unique_index as usize;
if buf.len() <= u {
return n;
}
(buf[u] != self.seed as u8) as usize
}
HashKind::UniqueIndex => {
let u = self.unique_index as usize;
if buf.len() <= u {
return n;
}
self.table[buf[u] as usize] as usize
}
HashKind::FrontHash2 => {
if buf.len() < 2 {
return n;
}
let v =
u16::from_le(unsafe { (buf.as_ptr() as *const u16).read_unaligned() }) as u64;
self.table[(bitmix(v, self.seed) & self.mask as u64) as usize] as usize
}
HashKind::FrontHash4 => {
if buf.len() < 4 {
return n;
}
let v =
u32::from_le(unsafe { (buf.as_ptr() as *const u32).read_unaligned() }) as u64;
self.table[(bitmix(v, self.seed) & self.mask as u64) as usize] as usize
}
HashKind::FrontHash8 => {
if buf.len() < 8 {
return n;
}
let v = unsafe { load_u64(buf, 0) };
let h = rich_bitmix(v, self.seed);
self.table[(h & self.mask as u64) as usize] as usize
}
HashKind::UniqueIndexSized => {
let len = match self.key_len(buf) {
Some(l) => l,
None => return n,
};
let u = self.unique_index as usize;
let h = bitmix((buf[u] as u64) | ((len as u64) << 8), self.seed);
self.table[(h & self.mask as u64) as usize] as usize
}
HashKind::UniquePerLength => {
let len = match self.key_len(buf) {
Some(l) => l,
None => return n,
};
let col = self.per_len[len] as usize;
if col >= len {
return n;
}
let h = bitmix((buf[col] as u64) | ((len as u64) << 8), self.seed);
self.table[(h & self.mask as u64) as usize] as usize
}
HashKind::FullFlat => {
let len = match self.key_len(buf) {
Some(l) => l,
None => return n,
};
let h = full_hash(buf, len, self.seed);
self.table[(h & self.mask as u64) as usize] as usize
}
HashKind::Linear => {
let len = match self.key_len(buf) {
Some(l) => l,
None => return n,
};
let key = &buf[..len];
let mut i = 0;
while i < keys.len() {
if keys[i].as_bytes() == key {
return i;
}
i += 1;
}
n
}
}
}
#[inline(always)]
pub fn lookup_sized(&self, keys: &[&'static str], key: &[u8]) -> usize {
let n = self.n as usize;
let len = key.len();
if len < self.min_len as usize || len > self.max_len as usize {
return n;
}
match self.kind {
HashKind::Empty => n,
HashKind::SingleElement => 0,
HashKind::Mod4 => (key[0] & 3) as usize,
HashKind::XorMod4 => ((key[0] ^ self.seed as u8) & 3) as usize,
HashKind::MinusMod4 => (key[0].wrapping_sub(self.seed as u8) & 3) as usize,
HashKind::UniqueIndexTwo => {
(key[self.unique_index as usize] != self.seed as u8) as usize
}
HashKind::UniqueIndex => self.table[key[self.unique_index as usize] as usize] as usize,
HashKind::FrontHash2 => {
let v =
u16::from_le(unsafe { (key.as_ptr() as *const u16).read_unaligned() }) as u64;
self.table[(bitmix(v, self.seed) & self.mask as u64) as usize] as usize
}
HashKind::FrontHash4 => {
let v =
u32::from_le(unsafe { (key.as_ptr() as *const u32).read_unaligned() }) as u64;
self.table[(bitmix(v, self.seed) & self.mask as u64) as usize] as usize
}
HashKind::FrontHash8 => {
let v = unsafe { load_u64(key, 0) };
let h = rich_bitmix(v, self.seed);
self.table[(h & self.mask as u64) as usize] as usize
}
HashKind::UniqueIndexSized => {
let u = self.unique_index as usize;
let h = bitmix((key[u] as u64) | ((len as u64) << 8), self.seed);
self.table[(h & self.mask as u64) as usize] as usize
}
HashKind::UniquePerLength => {
let col = self.per_len[len] as usize;
if col >= len {
return n;
}
let h = bitmix((key[col] as u64) | ((len as u64) << 8), self.seed);
self.table[(h & self.mask as u64) as usize] as usize
}
HashKind::FullFlat => {
let h = full_hash(key, len, self.seed);
self.table[(h & self.mask as u64) as usize] as usize
}
HashKind::Linear => {
let mut i = 0;
while i < keys.len() {
if keys[i].as_bytes() == key {
return i;
}
i += 1;
}
n
}
}
}
#[inline(always)]
fn key_len(&self, buf: &[u8]) -> Option<usize> {
let min = self.min_len as usize;
if buf.len() < min {
return None;
}
let end = find_quote(buf, min)?;
if end > self.max_len as usize {
return None;
}
Some(end)
}
}