pub struct CompressedMemory {
index: Vec<u32>,
bits: Vec<Page>,
}
#[allow(clippy::new_without_default)]
impl CompressedMemory {
const LOG_MAX_ADDR: usize = 40;
const ALIGNMENT: usize = 8;
const MAX_NUM_PAGES: usize = (1 << Self::LOG_MAX_ADDR) / Self::ALIGNMENT / Self::PAGE_SIZE;
const PAGE_SIZE: usize = 1 << 18;
#[must_use]
pub fn new() -> Self {
Self { index: vec![u32::MAX; Self::MAX_NUM_PAGES], bits: Vec::new() }
}
#[inline]
pub fn insert(&mut self, addr: u64, value: bool) -> bool {
let (upper, lower) = Self::indices(addr);
debug_assert!(upper < Self::MAX_NUM_PAGES, "address exceeds 40-bit range");
let page_id = match self.index[upper] {
u32::MAX => {
let id = self.bits.len() as u32;
self.bits.push(Page::new());
self.index[upper] = id;
id
}
id => id,
};
self.bits[page_id as usize].set(lower, value)
}
#[inline]
#[must_use]
pub fn get(&self, addr: u64) -> bool {
let (upper, lower) = Self::indices(addr);
if upper >= self.index.len() {
return false;
}
match self.index[upper] {
u32::MAX => false,
id => self.bits[id as usize].get(lower),
}
}
#[inline]
fn indices(addr: u64) -> (usize, usize) {
let compressed = (addr / Self::ALIGNMENT as u64) as usize;
let upper = compressed / Self::PAGE_SIZE; let lower = compressed % Self::PAGE_SIZE; (upper, lower)
}
#[must_use]
pub fn is_set(&self) -> Vec<u64> {
let mut out = Vec::new();
for (upper, &pid) in self.index.iter().enumerate() {
if pid == u32::MAX {
continue;
}
let base = upper * Self::PAGE_SIZE; for lower in self.bits[pid as usize].iter_set_indices() {
let compressed = base + lower;
out.push((compressed as u64) * Self::ALIGNMENT as u64);
}
}
out
}
}
pub struct Page {
bits: Vec<u64>,
}
impl Page {
pub fn new() -> Self {
Self { bits: vec![0; CompressedMemory::PAGE_SIZE / 64] }
}
#[inline]
fn word_mask(idx: usize) -> (usize, u64) {
let w = idx >> 6;
let m = 1u64 << (idx & 63);
(w, m)
}
#[inline]
fn set(&mut self, idx: usize, value: bool) -> bool {
let (w, m) = Self::word_mask(idx);
let prev = (self.bits[w] & m) != 0;
if value {
self.bits[w] |= m;
} else {
self.bits[w] &= !m;
}
prev
}
#[inline]
fn get(&self, idx: usize) -> bool {
let (w, m) = Self::word_mask(idx);
(self.bits[w] & m) != 0
}
#[inline]
fn iter_set_indices(&self) -> impl Iterator<Item = usize> + '_ {
self.bits.iter().enumerate().flat_map(|(w_i, &word0)| {
let mut word = word0;
std::iter::from_fn(move || {
if word == 0 {
return None;
}
let tz = word.trailing_zeros() as usize;
let idx = (w_i << 6) | tz;
word &= word - 1; Some(idx)
})
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn align_down(x: u64) -> u64 {
x & !((CompressedMemory::ALIGNMENT as u64) - 1)
}
#[test]
fn new_is_empty_and_false() {
let m = CompressedMemory::new();
assert!(!m.get(0));
assert!(!m.get(8));
assert!(!m.get(123456));
}
#[test]
fn basic_set_get_and_prev() {
let mut m = CompressedMemory::new();
assert!(!m.get(0));
assert!(!m.insert(0, true));
assert!(m.get(0));
assert!(m.insert(0, true));
assert!(m.get(0));
assert!(m.insert(0, false));
assert!(!m.get(0));
}
#[test]
fn unaligned_addresses_alias_same_bit() {
let mut m = CompressedMemory::new();
let a = 9u64; let aligned = align_down(a);
assert_eq!(aligned, 8);
m.insert(a, true);
assert!(m.get(aligned));
assert!(m.get(a));
assert!(m.get(aligned + (CompressedMemory::ALIGNMENT as u64) - 1));
assert!(!m.get(aligned + CompressedMemory::ALIGNMENT as u64));
}
#[test]
fn across_page_boundary() {
let mut m = CompressedMemory::new();
let a0 = 0u64;
let a1 = (CompressedMemory::PAGE_SIZE as u64) * (CompressedMemory::ALIGNMENT as u64);
m.insert(a0, true);
m.insert(a1, true);
assert!(m.get(a0));
assert!(m.get(a1));
}
#[test]
fn high_address_within_40_bits() {
let mut m = CompressedMemory::new();
let max_addr = ((1u128 << CompressedMemory::LOG_MAX_ADDR) - 1) as u64;
let max_aligned = align_down(max_addr);
let compressed = (max_aligned / CompressedMemory::ALIGNMENT as u64) as usize;
let upper = compressed / CompressedMemory::PAGE_SIZE;
let lower = compressed % CompressedMemory::PAGE_SIZE;
assert_eq!(upper, CompressedMemory::MAX_NUM_PAGES - 1);
assert_eq!(lower, CompressedMemory::PAGE_SIZE - 1);
assert!(!m.insert(max_aligned, true));
assert!(m.get(max_aligned));
if max_aligned >= CompressedMemory::ALIGNMENT as u64 {
assert!(!m.get(max_aligned - CompressedMemory::ALIGNMENT as u64));
}
assert_eq!(m.is_set(), vec![max_aligned]);
}
#[test]
#[allow(clippy::many_single_char_names)]
fn many_out_of_order_is_set_is_sorted() {
let mut m = CompressedMemory::new();
let a = 0u64;
let b = (CompressedMemory::PAGE_SIZE as u64 / 2) * (CompressedMemory::ALIGNMENT as u64);
let c = (CompressedMemory::PAGE_SIZE as u64) * (CompressedMemory::ALIGNMENT as u64); let d = c + 24;
m.insert(c, true);
m.insert(a, true);
m.insert(d, true); m.insert(b, true);
let expected = {
let mut v = vec![align_down(a), align_down(b), align_down(c), align_down(d)];
v.sort_unstable();
v.dedup();
v
};
let mut got = m.is_set();
got.sort_unstable();
assert_eq!(got, expected);
}
}