use std::ffi::{c_char, c_int, c_void, CStr};
use std::ptr;
use super::abi::*;
const REBUILD_MULTIPLIER: isize = 3;
const RANDOM_MULTIPLIER: usize = 1103515245;
pub unsafe fn init(table: *mut TclHashTable, key_type: c_int) {
assert!(
key_type >= TCL_STRING_KEYS,
"hash key type {key_type} is one of Tcl's custom-key disciplines \
(generic/tcl.h:1253-1254), which is not implemented"
);
(*table).static_buckets = [ptr::null_mut(); TCL_SMALL_HASH_TABLE];
(*table).buckets = ptr::addr_of_mut!((*table).static_buckets) as *mut *mut TclHashEntry;
(*table).num_buckets = TCL_SMALL_HASH_TABLE as isize;
(*table).num_entries = 0;
(*table).rebuild_size = TCL_SMALL_HASH_TABLE as isize * REBUILD_MULTIPLIER;
(*table).mask = 3;
(*table).down_shift = 28;
(*table).key_type = key_type;
(*table).find_proc = Some(find);
(*table).create_proc = Some(create);
(*table).type_ptr = ptr::null();
}
unsafe fn buckets_are_static(table: *mut TclHashTable) -> bool {
ptr::eq(
(*table).buckets as *const c_void,
ptr::addr_of!((*table).static_buckets) as *const c_void,
)
}
unsafe fn key_bytes(table: *mut TclHashTable, key: *const c_char) -> Vec<u8> {
match (*table).key_type {
TCL_STRING_KEYS => CStr::from_ptr(key).to_bytes().to_vec(),
TCL_ONE_WORD_KEYS => (key as usize).to_ne_bytes().to_vec(),
n => std::slice::from_raw_parts(key as *const u8, n as usize * 4).to_vec(),
}
}
unsafe fn entry_key_bytes(entry: *mut TclHashEntry) -> Vec<u8> {
let table = (*entry).table_ptr;
let inline = ptr::addr_of!((*entry).key) as *const u8;
match (*table).key_type {
TCL_STRING_KEYS => CStr::from_ptr(inline as *const c_char).to_bytes().to_vec(),
TCL_ONE_WORD_KEYS => ((*entry).key as usize).to_ne_bytes().to_vec(),
n => std::slice::from_raw_parts(inline, n as usize * 4).to_vec(),
}
}
fn hash_string(bytes: &[u8]) -> usize {
let mut result: usize = match bytes.first() {
None | Some(0) => return 0,
Some(b) => *b as usize,
};
for b in &bytes[1..] {
result = result.wrapping_add(result << 3).wrapping_add(*b as usize);
}
result
}
fn hash_array(bytes: &[u8]) -> usize {
let mut result: usize = 0;
for word in bytes.chunks_exact(4) {
let v = i32::from_ne_bytes([word[0], word[1], word[2], word[3]]);
result = result.wrapping_add(v as usize);
}
result
}
unsafe fn hash_of(table: *mut TclHashTable, key: &[u8], raw: *const c_char) -> usize {
match (*table).key_type {
TCL_STRING_KEYS => hash_string(key),
TCL_ONE_WORD_KEYS => raw as usize,
_ => hash_array(key),
}
}
unsafe fn bucket_of(table: *mut TclHashTable, hash: usize) -> usize {
if (*table).key_type == TCL_STRING_KEYS {
hash & (*table).mask
} else {
(hash.wrapping_mul(RANDOM_MULTIPLIER) >> (*table).down_shift) & (*table).mask
}
}
unsafe extern "C" fn find(table: *mut TclHashTable, key: *const c_char) -> *mut TclHashEntry {
let want = key_bytes(table, key);
let h = hash_of(table, &want, key);
let mut e = *(*table).buckets.add(bucket_of(table, h));
while !e.is_null() {
if (*e).hash == h && entry_key_bytes(e) == want {
return e;
}
e = (*e).next_ptr;
}
ptr::null_mut()
}
unsafe extern "C" fn create(
table: *mut TclHashTable,
key: *const c_char,
new_ptr: *mut c_int,
) -> *mut TclHashEntry {
let existing = find(table, key);
if !existing.is_null() {
if !new_ptr.is_null() {
*new_ptr = 0;
}
return existing;
}
let want = key_bytes(table, key);
let h = hash_of(table, &want, key);
let size = if (*table).key_type == TCL_ONE_WORD_KEYS {
std::mem::size_of::<TclHashEntry>()
} else {
let terminator = usize::from((*table).key_type == TCL_STRING_KEYS);
(std::mem::offset_of!(TclHashEntry, key) + want.len() + terminator)
.max(std::mem::size_of::<TclHashEntry>())
};
let e = libc::calloc(1, size) as *mut TclHashEntry;
assert!(!e.is_null(), "out of memory allocating hash entry");
(*e).table_ptr = table;
(*e).hash = h;
(*e).client_data = ptr::null_mut();
if (*table).key_type == TCL_ONE_WORD_KEYS {
(*e).key = key as *mut c_char;
} else {
let dst = ptr::addr_of_mut!((*e).key) as *mut u8;
ptr::copy_nonoverlapping(want.as_ptr(), dst, want.len());
if (*table).key_type == TCL_STRING_KEYS {
*dst.add(want.len()) = 0;
}
}
let b = (*table).buckets.add(bucket_of(table, h));
(*e).next_ptr = *b;
*b = e;
(*table).num_entries += 1;
if !new_ptr.is_null() {
*new_ptr = 1;
}
if (*table).num_entries >= (*table).rebuild_size {
rebuild(table);
}
e
}
pub unsafe fn rebuild(table: *mut TclHashTable) {
let old_size = (*table).num_buckets;
let old_buckets = (*table).buckets;
let old_was_static = buckets_are_static(table);
(*table).num_buckets *= 4;
let bytes = (*table).num_buckets as usize * std::mem::size_of::<*mut TclHashEntry>();
let fresh = libc::calloc(1, bytes) as *mut *mut TclHashEntry;
assert!(!fresh.is_null(), "out of memory rebuilding a hash table");
(*table).buckets = fresh;
(*table).rebuild_size *= 4;
if (*table).down_shift > 1 {
(*table).down_shift -= 2;
}
(*table).mask = ((*table).mask << 2) + 3;
for i in 0..old_size {
let mut e = *old_buckets.offset(i);
while !e.is_null() {
let next = (*e).next_ptr;
let b = fresh.add(bucket_of(table, (*e).hash));
(*e).next_ptr = *b;
*b = e;
e = next;
}
}
if !old_was_static {
libc::free(old_buckets as *mut c_void);
}
}
pub unsafe fn delete_entry(entry: *mut TclHashEntry) {
let table = (*entry).table_ptr;
let b = (*table).buckets.add(bucket_of(table, (*entry).hash));
let mut cur = *b;
if cur == entry {
*b = (*entry).next_ptr;
} else {
while !cur.is_null() && (*cur).next_ptr != entry {
cur = (*cur).next_ptr;
}
assert!(
!cur.is_null(),
"malformed bucket chain in Tcl_DeleteHashEntry \
(generic/tclHash.c:416-419)"
);
(*cur).next_ptr = (*entry).next_ptr;
}
(*table).num_entries -= 1;
libc::free(entry as *mut c_void);
}
pub unsafe fn first_entry(
table: *mut TclHashTable,
search: *mut TclHashSearch,
) -> *mut TclHashEntry {
(*search).table_ptr = table;
(*search).next_index = 0;
(*search).next_entry_ptr = ptr::null_mut();
next_entry(search)
}
pub unsafe fn next_entry(search: *mut TclHashSearch) -> *mut TclHashEntry {
let table = (*search).table_ptr;
loop {
if !(*search).next_entry_ptr.is_null() {
let e = (*search).next_entry_ptr;
(*search).next_entry_ptr = (*e).next_ptr;
return e;
}
if (*search).next_index >= (*table).num_buckets {
return ptr::null_mut();
}
(*search).next_entry_ptr = *(*table).buckets.offset((*search).next_index);
(*search).next_index += 1;
}
}
unsafe extern "C" fn bogus_find(
_table: *mut TclHashTable,
_key: *const c_char,
) -> *mut TclHashEntry {
panic!("called Tcl_FindHashEntry on a deleted table (generic/tclHash.c:899-907)");
}
unsafe extern "C" fn bogus_create(
_table: *mut TclHashTable,
_key: *const c_char,
_new: *mut c_int,
) -> *mut TclHashEntry {
panic!("called Tcl_CreateHashEntry on a deleted table (generic/tclHash.c:925-931)");
}
pub unsafe fn delete_table(table: *mut TclHashTable) {
for i in 0..(*table).num_buckets {
let mut e = *(*table).buckets.offset(i);
while !e.is_null() {
let next = (*e).next_ptr;
libc::free(e as *mut c_void);
e = next;
}
}
if !buckets_are_static(table) {
libc::free((*table).buckets as *mut c_void);
}
(*table).buckets = ptr::null_mut();
(*table).num_entries = 0;
(*table).num_buckets = 0;
(*table).find_proc = Some(bogus_find);
(*table).create_proc = Some(bogus_create);
}