use std::num::NonZeroUsize;
use std::sync::atomic::{AtomicU64, Ordering};
use crate::bitmap::{Bitmap, NewBitmap, RefSlice, WithBitmapSlice};
#[derive(Debug)]
pub struct AtomicBitmap {
map: Vec<AtomicU64>,
size: usize,
byte_size: usize,
page_size: NonZeroUsize,
}
#[allow(clippy::len_without_is_empty)]
impl AtomicBitmap {
pub fn new(byte_size: usize, page_size: NonZeroUsize) -> Self {
let num_pages = byte_size.div_ceil(page_size.get());
let map_size = num_pages.div_ceil(u64::BITS as usize);
let map: Vec<AtomicU64> = (0..map_size).map(|_| AtomicU64::new(0)).collect();
AtomicBitmap {
map,
size: num_pages,
byte_size,
page_size,
}
}
pub fn enlarge(&mut self, additional_size: usize) {
self.byte_size += additional_size;
self.size = self.byte_size.div_ceil(self.page_size.get());
let map_size = self.size.div_ceil(u64::BITS as usize);
self.map.resize_with(map_size, Default::default);
}
pub fn is_bit_set(&self, index: usize) -> bool {
if index < self.size {
(self.map[index >> 6].load(Ordering::Acquire) & (1 << (index & 63))) != 0
} else {
false
}
}
pub fn is_addr_set(&self, addr: usize) -> bool {
self.is_bit_set(addr / self.page_size)
}
pub fn set_addr_range(&self, start_addr: usize, len: usize) {
self.set_reset_addr_range(start_addr, len, true);
}
fn set_reset_addr_range(&self, start_addr: usize, len: usize, set: bool) {
if len == 0 {
return;
}
let first_bit = start_addr / self.page_size;
let last_bit = start_addr.saturating_add(len - 1) / self.page_size;
for n in first_bit..=last_bit {
if n >= self.size {
break;
}
if set {
self.map[n >> 6].fetch_or(1 << (n & 63), Ordering::SeqCst);
} else {
self.map[n >> 6].fetch_and(!(1 << (n & 63)), Ordering::SeqCst);
}
}
}
pub fn reset_addr_range(&self, start_addr: usize, len: usize) {
self.set_reset_addr_range(start_addr, len, false);
}
pub fn set_bit(&self, index: usize) {
if index >= self.size {
return;
}
self.map[index >> 6].fetch_or(1 << (index & 63), Ordering::SeqCst);
}
pub fn reset_bit(&self, index: usize) {
if index >= self.size {
return;
}
self.map[index >> 6].fetch_and(!(1 << (index & 63)), Ordering::SeqCst);
}
pub fn len(&self) -> usize {
self.size
}
pub fn byte_size(&self) -> usize {
self.byte_size
}
pub fn get_and_reset(&self) -> Vec<u64> {
self.map
.iter()
.map(|u| u.fetch_and(0, Ordering::SeqCst))
.collect()
}
pub fn reset(&self) {
for it in self.map.iter() {
it.store(0, Ordering::Release);
}
}
}
impl Clone for AtomicBitmap {
fn clone(&self) -> Self {
let map = self
.map
.iter()
.map(|i| i.load(Ordering::Acquire))
.map(AtomicU64::new)
.collect();
AtomicBitmap {
map,
size: self.size,
byte_size: self.byte_size,
page_size: self.page_size,
}
}
}
impl<'a> WithBitmapSlice<'a> for AtomicBitmap {
type S = RefSlice<'a, Self>;
}
impl Bitmap for AtomicBitmap {
fn mark_dirty(&self, offset: usize, len: usize) {
self.set_addr_range(offset, len)
}
fn dirty_at(&self, offset: usize) -> bool {
self.is_addr_set(offset)
}
fn slice_at(&self, offset: usize) -> <Self as WithBitmapSlice<'_>>::S {
RefSlice::new(self, offset)
}
}
impl Default for AtomicBitmap {
fn default() -> Self {
AtomicBitmap::new(0, const { NonZeroUsize::new(0x1000).unwrap() })
}
}
impl NewBitmap for AtomicBitmap {
fn with_len(len: usize) -> Self {
#[cfg(target_family = "unix")]
let page_size = unsafe { libc::sysconf(libc::_SC_PAGE_SIZE) };
#[cfg(target_family = "windows")]
let page_size = {
use winapi::um::sysinfoapi::GetSystemInfo;
let mut sysinfo = std::mem::MaybeUninit::zeroed();
unsafe { GetSystemInfo(sysinfo.as_mut_ptr()) };
unsafe { sysinfo.assume_init().dwPageSize }
};
AtomicBitmap::new(
len,
NonZeroUsize::try_from(usize::try_from(page_size).unwrap()).unwrap(),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bitmap::tests::test_bitmap;
#[allow(clippy::undocumented_unsafe_blocks)]
const DEFAULT_PAGE_SIZE: NonZeroUsize = NonZeroUsize::new(128).unwrap();
#[test]
fn test_bitmap_basic() {
let a = AtomicBitmap::new(1025, DEFAULT_PAGE_SIZE);
assert_eq!(a.len(), 9);
let b = AtomicBitmap::new(1024, DEFAULT_PAGE_SIZE);
assert_eq!(b.len(), 8);
b.set_addr_range(128, 129);
assert!(!b.is_addr_set(0));
assert!(b.is_addr_set(128));
assert!(b.is_addr_set(256));
assert!(!b.is_addr_set(384));
#[allow(clippy::redundant_clone)]
let copy_b = b.clone();
assert!(copy_b.is_addr_set(256));
assert!(!copy_b.is_addr_set(384));
b.reset();
assert!(!b.is_addr_set(128));
assert!(!b.is_addr_set(256));
assert!(!b.is_addr_set(384));
b.set_addr_range(128, 129);
let v = b.get_and_reset();
assert!(!b.is_addr_set(128));
assert!(!b.is_addr_set(256));
assert!(!b.is_addr_set(384));
assert_eq!(v.len(), 1);
assert_eq!(v[0], 0b110);
}
#[test]
fn test_bitmap_reset() {
let b = AtomicBitmap::new(1024, DEFAULT_PAGE_SIZE);
assert_eq!(b.len(), 8);
b.set_addr_range(128, 129);
assert!(!b.is_addr_set(0));
assert!(b.is_addr_set(128));
assert!(b.is_addr_set(256));
assert!(!b.is_addr_set(384));
b.reset_addr_range(128, 129);
assert!(!b.is_addr_set(0));
assert!(!b.is_addr_set(128));
assert!(!b.is_addr_set(256));
assert!(!b.is_addr_set(384));
}
#[test]
fn test_bitmap_out_of_range() {
let b = AtomicBitmap::new(1024, NonZeroUsize::MIN);
b.set_addr_range(768, 512);
assert!(b.is_addr_set(768));
assert!(!b.is_addr_set(1024));
assert!(!b.is_addr_set(1152));
}
#[test]
fn test_bitmap_impl() {
let b = AtomicBitmap::new(0x800, DEFAULT_PAGE_SIZE);
test_bitmap(&b);
}
#[test]
fn test_bitmap_enlarge() {
let mut b = AtomicBitmap::new(8 * 1024, DEFAULT_PAGE_SIZE);
assert_eq!(b.len(), 64);
b.set_addr_range(128, 129);
assert!(!b.is_addr_set(0));
assert!(b.is_addr_set(128));
assert!(b.is_addr_set(256));
assert!(!b.is_addr_set(384));
b.reset_addr_range(128, 129);
assert!(!b.is_addr_set(0));
assert!(!b.is_addr_set(128));
assert!(!b.is_addr_set(256));
assert!(!b.is_addr_set(384));
b.set_addr_range(128, 129);
b.enlarge(8 * 1024);
for i in 65..128 {
assert!(!b.is_bit_set(i));
}
assert_eq!(b.len(), 128);
assert!(!b.is_addr_set(0));
assert!(b.is_addr_set(128));
assert!(b.is_addr_set(256));
assert!(!b.is_addr_set(384));
b.set_bit(55);
assert!(b.is_bit_set(55));
for i in 65..128 {
b.set_bit(i);
}
for i in 65..128 {
assert!(b.is_bit_set(i));
}
b.reset_addr_range(0, 16 * 1024);
for i in 0..128 {
assert!(!b.is_bit_set(i));
}
}
}