use super::state_table::StateTable;
pub struct ContextMap {
table: Vec<u8>,
mask: usize,
}
impl ContextMap {
pub fn new(size: usize) -> Self {
debug_assert!(size.is_power_of_two(), "ContextMap size must be power of 2");
ContextMap {
table: vec![0u8; size],
mask: size - 1,
}
}
#[inline(always)]
pub fn get(&self, hash: u32) -> u8 {
self.table[hash as usize & self.mask]
}
#[inline(always)]
pub fn set(&mut self, hash: u32, state: u8) {
self.table[hash as usize & self.mask] = state;
}
#[inline]
pub fn predict_and_update(&mut self, hash: u32, bit: u8) -> u8 {
let idx = hash as usize & self.mask;
let state = self.table[idx];
self.table[idx] = StateTable::next(state, bit);
state
}
}
pub struct ChecksumContextMap {
table: Vec<u8>,
mask: usize,
}
impl ChecksumContextMap {
pub fn new(byte_size: usize) -> Self {
debug_assert!(
byte_size.is_power_of_two(),
"ChecksumContextMap size must be power of 2"
);
let entries = byte_size / 2;
ChecksumContextMap {
table: vec![0u8; byte_size],
mask: entries - 1,
}
}
#[inline(always)]
fn checksum(hash: u32) -> u8 {
((hash >> 16) as u8) | 1 }
#[inline(always)]
pub fn get(&self, hash: u32) -> u8 {
let idx = (hash as usize & self.mask) * 2;
let stored_cs = self.table[idx];
let expected_cs = Self::checksum(hash);
if stored_cs == expected_cs {
self.table[idx + 1]
} else {
0
}
}
#[inline(always)]
pub fn set(&mut self, hash: u32, state: u8) {
let idx = (hash as usize & self.mask) * 2;
self.table[idx] = Self::checksum(hash);
self.table[idx + 1] = state;
}
}
pub struct AssociativeContextMap {
table: Vec<u8>,
mask: usize,
}
impl AssociativeContextMap {
pub fn new(byte_size: usize) -> Self {
debug_assert!(
byte_size.is_power_of_two() && byte_size >= 8,
"AssociativeContextMap size must be power of 2 and >= 8"
);
let sets = byte_size / 4;
AssociativeContextMap {
table: vec![0u8; byte_size],
mask: sets - 1,
}
}
#[inline(always)]
fn checksum(hash: u32) -> u8 {
((hash >> 16) as u8) | 1
}
#[inline(always)]
pub fn get(&self, hash: u32) -> u8 {
let base = (hash as usize & self.mask) * 4;
let cs = Self::checksum(hash);
if self.table[base] == cs {
return self.table[base + 1];
}
if self.table[base + 2] == cs {
return self.table[base + 3];
}
0
}
#[inline(always)]
pub fn set(&mut self, hash: u32, state: u8) {
let base = (hash as usize & self.mask) * 4;
let cs = Self::checksum(hash);
if self.table[base] == cs {
self.table[base + 1] = state;
return;
}
if self.table[base + 2] == cs {
self.table[base + 3] = state;
return;
}
self.table[base + 2] = self.table[base];
self.table[base + 3] = self.table[base + 1];
self.table[base] = cs;
self.table[base + 1] = state;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_entries_are_zero() {
let cm = ContextMap::new(1024);
assert_eq!(cm.get(0), 0);
assert_eq!(cm.get(999), 0);
}
#[test]
fn set_and_get() {
let mut cm = ContextMap::new(1024);
cm.set(42, 128);
assert_eq!(cm.get(42), 128);
}
#[test]
fn hash_masking() {
let mut cm = ContextMap::new(256);
cm.set(0, 10);
assert_eq!(cm.get(256), 10);
}
#[test]
fn predict_and_update_transitions() {
let mut cm = ContextMap::new(1024);
let state = cm.predict_and_update(42, 1);
assert_eq!(state, 0);
let new_state = cm.get(42);
assert_ne!(new_state, 0);
}
#[test]
fn lossy_collision() {
let mut cm = ContextMap::new(256);
cm.set(5, 100);
cm.set(5 + 256, 200);
assert_eq!(cm.get(5), 200);
}
#[test]
fn checksum_new_entries_return_zero() {
let cm = ChecksumContextMap::new(2048);
assert_eq!(cm.get(0), 0);
assert_eq!(cm.get(12345), 0);
}
#[test]
fn checksum_set_and_get() {
let mut cm = ChecksumContextMap::new(2048);
cm.set(42, 128);
assert_eq!(cm.get(42), 128);
}
#[test]
fn checksum_overwrites_properly() {
let mut cm = ChecksumContextMap::new(2048);
cm.set(42, 100);
cm.set(42, 200);
assert_eq!(cm.get(42), 200);
}
#[test]
fn assoc_new_entries_return_zero() {
let cm = AssociativeContextMap::new(4096);
assert_eq!(cm.get(0), 0);
assert_eq!(cm.get(12345), 0);
}
#[test]
fn assoc_set_and_get() {
let mut cm = AssociativeContextMap::new(4096);
cm.set(42, 128);
assert_eq!(cm.get(42), 128);
}
#[test]
fn assoc_two_entries_same_set() {
let mut cm = AssociativeContextMap::new(16); cm.set(0x00010000, 100); cm.set(0x00020000, 200); assert_eq!(cm.get(0x00010000), 100);
assert_eq!(cm.get(0x00020000), 200);
}
#[test]
fn assoc_overwrites_properly() {
let mut cm = AssociativeContextMap::new(4096);
cm.set(42, 100);
cm.set(42, 200);
assert_eq!(cm.get(42), 200);
}
}