use std::isize;
use std::cell::{UnsafeCell, Cell};
use std::cmp::min;
use std::mem::{transmute, size_of};
use value::*;
use depth::*;
use hash_table::*;
use moves::MoveDigest;
#[derive(Copy, Clone, Debug)]
pub struct StdHashTableEntry {
value: Value,
gen_bound: u8,
depth: Depth,
move_digest: MoveDigest,
static_eval: Value,
}
impl HashTableEntry for StdHashTableEntry {
#[inline]
fn new(value: Value,
bound: BoundType,
depth: Depth,
move_digest: MoveDigest)
-> StdHashTableEntry {
Self::with_static_eval(value, bound, depth, move_digest, VALUE_UNKNOWN)
}
#[inline]
fn with_static_eval(value: Value,
bound: BoundType,
depth: Depth,
move_digest: MoveDigest,
static_eval: Value)
-> StdHashTableEntry {
debug_assert!(value != VALUE_UNKNOWN);
debug_assert!(bound <= 0b11);
debug_assert!(DEPTH_MIN <= depth && depth <= DEPTH_MAX);
StdHashTableEntry {
value: value,
gen_bound: bound,
depth: depth,
move_digest: move_digest,
static_eval: static_eval,
}
}
#[inline]
fn value(&self) -> Value {
self.value
}
#[inline]
fn bound(&self) -> BoundType {
self.gen_bound & 0b11
}
#[inline]
fn depth(&self) -> Depth {
self.depth
}
#[inline]
fn move_digest(&self) -> MoveDigest {
self.move_digest
}
#[inline]
fn static_eval(&self) -> Value {
self.static_eval
}
}
impl StdHashTableEntry {
#[inline]
fn as_u64(&self) -> u64 {
unsafe { transmute(*self) }
}
}
pub struct StdHashTable {
generation: Cell<u8>,
cluster_count: usize,
table: UnsafeCell<Vec<[Record; 4]>>,
}
impl HashTable for StdHashTable {
type Entry = StdHashTableEntry;
fn new(size_mb: Option<usize>) -> StdHashTable {
let size_mb = size_mb.unwrap_or(16);
let requested_cluster_count = (size_mb * 1024 * 1024) / size_of::<[Record; 4]>();
let mut n = 1;
while n <= requested_cluster_count && n != 0 {
n <<= 1;
}
if n > 1 {
n >>= 1;
} else {
n = 1;
}
assert!(n > 0);
StdHashTable {
generation: Cell::new(0),
cluster_count: n,
table: UnsafeCell::new(vec![Default::default(); n]),
}
}
fn new_search(&self) {
const N: usize = 128;
loop {
self.generation.set(self.generation.get().wrapping_add(0b100));
debug_assert_eq!(self.generation.get() & 0b11, 0);
let mut staled = 0;
let mut cluster_iter = unsafe { &*self.table.get() }.iter();
for _ in 0..min(N, self.cluster_count) {
for record in cluster_iter.next().unwrap() {
if record.key != 0 && record.generation() == self.generation.get() {
staled += 1;
}
}
}
if staled < N {
break;
}
}
}
fn store(&self, key: u64, mut data: Self::Entry) {
data.gen_bound = self.generation.get() | data.bound();
let mut cluster = unsafe { self.cluster_mut(key) };
let mut replace_index = 0;
let mut replace_score = isize::MAX;
for (i, record) in cluster.iter_mut().enumerate() {
if record.key == 0 || record.key ^ record.data.as_u64() == key {
if data.move_digest == MoveDigest::invalid() {
data.move_digest = record.data.move_digest; }
replace_index = i;
break;
}
let record_score = self.calc_score(record);
if record_score < replace_score {
replace_index = i;
replace_score = record_score;
}
}
cluster[replace_index] = Record {
key: key ^ data.as_u64(),
data: data,
};
}
#[inline]
fn probe(&self, key: u64) -> Option<Self::Entry> {
let cluster = unsafe { self.cluster_mut(key) };
for record in cluster.iter_mut() {
if record.key ^ record.data.as_u64() == key {
record.set_generation(self.generation.get());
return Some(record.data);
}
}
None
}
fn clear(&self) {
let table = unsafe { &mut *self.table.get() };
for cluster in table {
for record in cluster.iter_mut() {
*record = Default::default();
}
}
self.generation.set(0);
}
}
impl StdHashTable {
#[inline(always)]
fn calc_score(&self, record: &Record) -> isize {
(if record.generation() == self.generation.get() {
DEPTH_MAX as isize + 2
} else {
0
})
+ record.data.depth() as isize
+ (if record.data.bound() == BOUND_EXACT {
1
} else {
0
})
}
#[inline(always)]
unsafe fn cluster_mut(&self, key: u64) -> &mut [Record; 4] {
let cluster_index = (key & (self.cluster_count - 1) as u64) as usize;
&mut (&mut *self.table.get())[cluster_index]
}
}
unsafe impl Sync for StdHashTable {}
#[derive(Copy, Clone)]
struct Record {
key: u64,
data: StdHashTableEntry,
}
impl Default for Record {
fn default() -> Record {
Record {
key: 0,
data: unsafe { transmute(0u64) },
}
}
}
impl Record {
#[inline]
fn generation(&self) -> u8 {
self.data.gen_bound & 0b11111100
}
#[inline(always)]
fn set_generation(&mut self, generation: u8) {
debug_assert_eq!(generation & 0b11, 0);
let old_data_as_u64 = self.data.as_u64();
self.data.gen_bound = generation | self.data.bound();
self.key ^= old_data_as_u64 ^ self.data.as_u64();
}
}
#[cfg(test)]
mod tests {
use super::*;
use super::Record;
use std;
use depth::*;
use hash_table::*;
use moves::*;
#[test]
fn cluster_size() {
assert_eq!(std::mem::size_of::<[Record; 4]>(), 64);
assert_eq!(std::mem::size_of::<Record>(), 16);
}
#[test]
fn store_and_probe() {
let tt = StdHashTable::new(None);
assert!(tt.probe(1).is_none());
let data = StdHashTableEntry::new(0, 0, 50, MoveDigest::invalid());
assert_eq!(data.depth(), 50);
assert_eq!(data.move_digest(), MoveDigest::invalid());
tt.store(1, data);
assert_eq!(tt.probe(1).unwrap().depth(), 50);
tt.store(1, StdHashTableEntry::new(0, 0, 50, MoveDigest::invalid()));
assert_eq!(tt.probe(1).unwrap().depth(), 50);
assert_eq!(tt.probe(1).unwrap().move_digest(), MoveDigest::invalid());
let digest = MoveDigest::invalid();
for i in 2..50 {
tt.store(i, StdHashTableEntry::new(i as i16, 0, i as Depth, digest));
}
assert_eq!(tt.probe(1).unwrap().depth(), 50);
assert_eq!(tt.probe(49).unwrap().depth(), 49);
assert_eq!(tt.probe(48).unwrap().depth(), 48);
assert_eq!(tt.probe(47).unwrap().depth(), 47);
tt.clear();
assert!(tt.probe(1).is_none());
tt.store(1, data);
tt.new_search();
tt.probe(1);
assert!(tt.probe(1).is_some());
}
#[test]
fn new_search() {
let tt = StdHashTable::new(None);
assert_eq!(tt.generation.get(), 0 << 2);
tt.new_search();
assert_eq!(tt.generation.get(), 1 << 2);
for _ in 0..64 {
tt.new_search();
}
assert_eq!(tt.generation.get(), 1 << 2);
}
}