use libc;
use libc::c_void;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::marker::PhantomData;
use std::isize;
use std::cell::Cell;
use std::cmp::max;
use std::mem;
use ttable::*;
use moves::MoveDigest;
#[derive(Copy, Clone)]
struct Record<T: TtableEntry> {
key: (u16, u16),
data: T,
}
struct Bucket<R> {
first: *mut R,
info: *mut AtomicUsize,
}
const BUCKET_SIZE: usize = 64;
impl<R> Bucket<R> {
#[inline]
pub unsafe fn new(p: *mut c_void) -> Bucket<R> {
let byte_offset = BUCKET_SIZE - mem::size_of::<usize>();
let info = (p.offset(byte_offset as isize) as *mut AtomicUsize)
.as_mut()
.unwrap();
loop {
let old = info.load(Ordering::Relaxed);
if old & BUCKET_LOCKING_FLAG == 0 {
let new = old | BUCKET_LOCKING_FLAG;
if info.compare_exchange_weak(old, new, Ordering::Acquire, Ordering::Relaxed)
.is_ok() {
break;
}
}
}
Bucket {
first: p as *mut R,
info: info as *mut AtomicUsize,
}
}
#[inline]
pub fn len() -> usize {
(BUCKET_SIZE - 4) / mem::size_of::<R>()
}
#[inline]
pub fn get(&self, slot: usize) -> *mut R {
assert!(slot < Self::len());
unsafe { self.first.offset(slot as isize) }
}
#[inline]
pub fn get_generation(&self, slot: usize) -> usize {
let info = unsafe { self.info.as_mut().unwrap() };
info.load(Ordering::Relaxed) >> GENERATION_SHIFTS[slot] & 31
}
#[inline]
pub fn set_generation(&self, slot: usize, generation: usize) {
debug_assert!(generation <= 31);
let info = unsafe { self.info.as_mut().unwrap() };
let mut v = info.load(Ordering::Relaxed);
v &= GENERATION_MASKS[slot];
v |= generation << GENERATION_SHIFTS[slot];
info.store(v, Ordering::Relaxed);
}
}
impl<R> Drop for Bucket<R> {
#[inline]
fn drop(&mut self) {
let info = unsafe { self.info.as_mut().unwrap() };
let old = info.load(Ordering::Relaxed);
let new = old & !BUCKET_LOCKING_FLAG;
info.store(new, Ordering::Release);
}
}
pub struct StdTtable<T: TtableEntry> {
entries: PhantomData<T>,
generation: Cell<usize>,
bucket_count: usize,
alloc_ptr: *mut c_void,
table_ptr: *mut c_void,
}
impl<T: TtableEntry> Ttable for StdTtable<T> {
type Entry = T;
fn new(size_mb: Option<usize>) -> StdTtable<T> {
assert_eq!(mem::size_of::<c_void>(), 1);
assert_eq!(BUCKET_SIZE, 64);
assert!(mem::align_of::<T>() <= 4,
format!("too restrictive transposition table entry alignment: {} bytes",
mem::align_of::<T>()));
assert!(Bucket::<Record<T>>::len() >= 3,
format!("too big transposition table entry: {} bytes",
mem::size_of::<T>()));
assert!(Bucket::<Record<T>>::len() <= 6,
format!("too small transposition table entry: {} bytes",
mem::size_of::<T>()));
let size_mb = size_mb.unwrap_or(16);
let bucket_count = {
let n = max(1, ((size_mb * 1024 * 1024) / BUCKET_SIZE) as u64);
1 << (63 - n.leading_zeros())
};
let alloc_ptr;
let table_ptr = unsafe {
alloc_ptr = libc::calloc(bucket_count + 1, BUCKET_SIZE);
let mut addr = mem::transmute::<*mut c_void, usize>(alloc_ptr);
addr += BUCKET_SIZE;
addr &= !(BUCKET_SIZE - 1);
mem::transmute::<usize, *mut c_void>(addr)
};
StdTtable {
entries: PhantomData,
generation: Cell::new(1),
bucket_count: bucket_count,
alloc_ptr: alloc_ptr,
table_ptr: table_ptr,
}
}
fn new_search(&self) {
const N: usize = 128;
loop {
self.generation
.set(match self.generation.get() {
n @ 1...30 => n + 1,
31 => 1,
_ => unreachable!(),
});
debug_assert!(self.generation.get() > 0);
debug_assert!(self.generation.get() < 32);
let mut staled = 0;
for bucket in self.buckets().take(N) {
for slot in 0..Bucket::<Record<T>>::len() {
if bucket.get_generation(slot) == self.generation.get() {
staled += 1;
}
}
}
if staled < N {
break;
}
}
}
#[inline]
fn store(&self, key: u64, mut data: Self::Entry) {
let bucket = self.bucket(key);
let key = chop_key(key);
let mut replace_slot = 0;
let mut replace_score = isize::MAX;
for slot in 0..Bucket::<Record<T>>::len() {
let record = unsafe { &mut *bucket.get(slot) };
let generation = bucket.get_generation(slot);
if generation == 0 {
replace_slot = slot;
break;
}
if record.key == key {
if record.data.bound() == BOUND_EXACT &&
record.data.importance() > data.importance() {
data = record.data;
} else if data.move_digest() == MoveDigest::invalid() {
data = data.set_move_digest(record.data.move_digest());
}
replace_slot = slot;
break;
}
let mut score = record.data.importance() as isize;
if generation == self.generation.get() {
score += 1 << 16;
};
if score < replace_score {
replace_slot = slot;
replace_score = score;
}
}
unsafe {
*bucket.get(replace_slot) = Record {
key: key,
data: data,
};
bucket.set_generation(replace_slot, self.generation.get());
}
}
#[inline]
fn probe(&self, key: u64) -> Option<Self::Entry> {
let bucket = self.bucket(key);
let key = chop_key(key);
for slot in 0..Bucket::<Record<T>>::len() {
if bucket.get_generation(slot) != 0 {
let record = unsafe { &mut *bucket.get(slot) };
if record.key == key {
bucket.set_generation(slot, self.generation.get());
return Some(record.data);
}
}
}
None
}
fn clear(&self) {
for bucket in self.buckets() {
for slot in 0..Bucket::<Record<T>>::len() {
bucket.set_generation(slot, 0);
}
}
self.generation.set(1);
}
}
impl<T: TtableEntry> StdTtable<T> {
#[inline]
fn bucket(&self, key: u64) -> Bucket<Record<T>> {
unsafe {
let byte_offset = (key as usize & (self.bucket_count - 1)) * BUCKET_SIZE;
Bucket::new(self.table_ptr.offset(byte_offset as isize))
}
}
#[inline]
fn buckets(&self) -> Iter<T> {
Iter {
entries: PhantomData,
table_ptr: self.table_ptr,
bucket_count: self.bucket_count,
iterated: 0,
}
}
}
impl<T: TtableEntry> Drop for StdTtable<T> {
fn drop(&mut self) {
unsafe {
libc::free(self.alloc_ptr);
}
}
}
unsafe impl<T: TtableEntry> Sync for StdTtable<T> {}
unsafe impl<T: TtableEntry> Send for StdTtable<T> {}
struct Iter<T: TtableEntry> {
entries: PhantomData<T>,
table_ptr: *mut c_void,
bucket_count: usize,
iterated: usize,
}
impl<T: TtableEntry> Iterator for Iter<T> {
type Item = Bucket<Record<T>>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
debug_assert!(self.iterated <= self.bucket_count);
if self.iterated == self.bucket_count {
None
} else {
let byte_offset = (self.iterated * BUCKET_SIZE) as isize;
let bucket = unsafe { Bucket::new(self.table_ptr.offset(byte_offset)) };
self.iterated += 1;
Some(bucket)
}
}
}
#[inline]
fn chop_key(key: u64) -> (u16, u16) {
unsafe { mem::transmute::<u32, (u16, u16)>((key >> 32) as u32) }
}
#[cfg(any(target_pointer_width = "32", target_endian = "big"))]
const BUCKET_LOCKING_FLAG: usize = 1 << 31;
#[cfg(all(target_pointer_width = "64", target_endian = "little"))]
const BUCKET_LOCKING_FLAG: usize = 1 << 63;
#[cfg(any(target_pointer_width = "32", target_endian = "big"))]
const GENERATION_SHIFTS: [usize; 6] = [0, 5, 10, 15, 20, 25];
#[cfg(all(target_pointer_width = "64", target_endian = "little"))]
const GENERATION_SHIFTS: [usize; 6] = [32, 37, 42, 47, 52, 57];
#[cfg(any(target_pointer_width = "32", target_endian = "big"))]
const GENERATION_MASKS: [usize; 6] = [!(31 << 0),
!(31 << 5),
!(31 << 10),
!(31 << 15),
!(31 << 20),
!(31 << 25)];
#[cfg(all(target_pointer_width = "64", target_endian = "little"))]
const GENERATION_MASKS: [usize; 6] = [!(31 << 32),
!(31 << 37),
!(31 << 42),
!(31 << 47),
!(31 << 52),
!(31 << 57)];
#[cfg(test)]
mod tests {
use libc;
use super::*;
use super::{Bucket, Record};
use depth::*;
use value::*;
use moves::*;
use stock::std_ttable_entry::*;
#[test]
fn bucket() {
unsafe {
let p = libc::calloc(1, 64);
let b = Bucket::<Record<StdTtableEntry>>::new(p);
assert_eq!(b.get_generation(0), 0);
assert_eq!(b.get_generation(1), 0);
let mut record = b.get(0).as_mut().unwrap();
let entry = StdTtableEntry::new(0, BOUND_NONE, 10);
*record = Record {
key: (0, 0),
data: entry,
};
b.set_generation(0, 12);
b.set_generation(1, 13);
assert_eq!(record.data.depth(), 10);
assert_eq!(b.get_generation(0), 12);
assert_eq!(b.get_generation(1), 13);
assert_eq!(Bucket::<Record<StdTtableEntry>>::len(), 5);
libc::free(p);
}
}
#[test]
fn bucket_endianness() {
unsafe {
let p = libc::calloc(1, 64);
let b = Bucket::<Record<StdTtableEntry>>::new(p);
let mut record = b.get(4).as_mut().unwrap();
let entry = StdTtableEntry::new(0, BOUND_NONE, 10);
*record = Record {
key: (0, 0),
data: entry,
};
b.set_generation(0, 12);
b.set_generation(1, 12);
b.set_generation(2, 12);
b.set_generation(3, 12);
b.set_generation(4, 12);
assert_eq!(record.data.static_eval(), VALUE_UNKNOWN);
libc::free(p);
}
}
#[test]
fn store_and_probe() {
let tt = StdTtable::<StdTtableEntry>::new(None);
assert!(tt.probe(1).is_none());
let data = StdTtableEntry::new(0, 0, 50);
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, StdTtableEntry::new(0, 0, 50));
assert_eq!(tt.probe(1).unwrap().depth(), 50);
assert_eq!(tt.probe(1).unwrap().move_digest(), MoveDigest::invalid());
for i in 2..50 {
tt.store(i, StdTtableEntry::new(i as i16, 0, i as Depth));
}
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 = StdTtable::<StdTtableEntry>::new(None);
assert_eq!(tt.generation.get(), 1);
tt.new_search();
assert_eq!(tt.generation.get(), 2);
for _ in 3..34 {
tt.new_search();
}
assert_eq!(tt.generation.get(), 2);
}
}