use crate::page::{PageType, DATA_PAGE_HEADER_SIZE, PAGE_BODY_SIZE, PAGE_SIZE};
const BITMAP_OFFSET: usize = DATA_PAGE_HEADER_SIZE;
pub struct FreeMap;
#[allow(dead_code)]
impl FreeMap {
pub fn capacity() -> usize {
PAGE_BODY_SIZE * 8
}
pub fn init_page(buf: &mut [u8; PAGE_SIZE]) {
buf.fill(0);
buf[0] = PageType::FreeMap as u8;
buf[1] = crate::page::current_version(crate::page::PageType::FreeMap);
}
pub fn is_free(buf: &[u8; PAGE_SIZE], page_id: u64) -> bool {
let (byte_idx, bit_idx) = Self::bit_position(page_id);
if byte_idx >= PAGE_BODY_SIZE {
return false;
}
(buf[BITMAP_OFFSET + byte_idx] >> bit_idx) & 1 == 1
}
pub fn mark_free(buf: &mut [u8; PAGE_SIZE], page_id: u64) {
let (byte_idx, bit_idx) = Self::bit_position(page_id);
if byte_idx < PAGE_BODY_SIZE {
buf[BITMAP_OFFSET + byte_idx] |= 1 << bit_idx;
}
}
pub fn allocate_first(buf: &mut [u8; PAGE_SIZE]) -> Option<u64> {
for byte_idx in 0..PAGE_BODY_SIZE {
let byte = buf[BITMAP_OFFSET + byte_idx];
if byte != 0 {
let bit_idx = byte.trailing_zeros() as usize;
let page_id = (byte_idx * 8 + bit_idx) as u64;
buf[BITMAP_OFFSET + byte_idx] &= !(1 << bit_idx);
return Some(page_id);
}
}
None
}
#[allow(dead_code)]
pub fn first_free_bit_from(buf: &[u8; PAGE_SIZE], lo: u64) -> Option<u64> {
let start_byte = (lo / 8) as usize;
if start_byte >= PAGE_BODY_SIZE {
return None;
}
let start_bit = (lo % 8) as u32;
for byte_idx in start_byte..PAGE_BODY_SIZE {
let mut byte = buf[BITMAP_OFFSET + byte_idx];
if byte_idx == start_byte {
byte &= !((1u8 << start_bit).wrapping_sub(1));
}
if byte != 0 {
return Some((byte_idx * 8 + byte.trailing_zeros() as usize) as u64);
}
}
None
}
#[allow(dead_code)]
pub fn clear_bit(buf: &mut [u8; PAGE_SIZE], page_id: u64) {
let (byte_idx, bit_idx) = Self::bit_position(page_id);
if byte_idx < PAGE_BODY_SIZE {
buf[BITMAP_OFFSET + byte_idx] &= !(1 << bit_idx);
}
}
fn bit_position(page_id: u64) -> (usize, usize) {
let page_id = page_id as usize;
(page_id / 8, page_id % 8)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_freemap_allocate_first_free() {
let mut buf = [0u8; PAGE_SIZE];
FreeMap::init_page(&mut buf);
FreeMap::mark_free(&mut buf, 50);
FreeMap::mark_free(&mut buf, 200);
let alloc = FreeMap::allocate_first(&mut buf);
assert_eq!(alloc, Some(50));
let alloc2 = FreeMap::allocate_first(&mut buf);
assert_eq!(alloc2, Some(200));
let alloc3 = FreeMap::allocate_first(&mut buf);
assert_eq!(alloc3, None);
}
#[test]
fn test_freemap_capacity() {
assert_eq!(FreeMap::capacity(), PAGE_BODY_SIZE * 8);
}
#[test]
fn first_free_bit_from_returns_lowest_at_or_above_lo() {
let mut buf = [0u8; PAGE_SIZE];
FreeMap::init_page(&mut buf);
FreeMap::mark_free(&mut buf, 12);
FreeMap::mark_free(&mut buf, 40);
FreeMap::mark_free(&mut buf, 300);
assert_eq!(FreeMap::first_free_bit_from(&buf, 0), Some(12));
assert_eq!(FreeMap::first_free_bit_from(&buf, 12), Some(12));
assert_eq!(FreeMap::first_free_bit_from(&buf, 13), Some(40));
assert_eq!(FreeMap::first_free_bit_from(&buf, 41), Some(300));
}
#[test]
fn first_free_bit_from_is_none_when_all_below_lo() {
let mut buf = [0u8; PAGE_SIZE];
FreeMap::init_page(&mut buf);
FreeMap::mark_free(&mut buf, 5);
FreeMap::mark_free(&mut buf, 100);
assert_eq!(FreeMap::first_free_bit_from(&buf, 101), None);
assert_eq!(
FreeMap::first_free_bit_from(&buf, PAGE_BODY_SIZE as u64 * 8),
None
);
}
#[test]
fn clear_bit_clears_named_id_and_leaves_others() {
let mut buf = [0u8; PAGE_SIZE];
FreeMap::init_page(&mut buf);
FreeMap::mark_free(&mut buf, 7);
FreeMap::mark_free(&mut buf, 8);
FreeMap::mark_free(&mut buf, 9);
FreeMap::clear_bit(&mut buf, 8);
assert!(FreeMap::is_free(&buf, 7), "7 must remain free");
assert!(!FreeMap::is_free(&buf, 8), "8 must be cleared");
assert!(FreeMap::is_free(&buf, 9), "9 must remain free");
}
proptest::proptest! {
#[test]
fn prop_mark_free_then_is_free(page_id in 0u64..(PAGE_BODY_SIZE as u64 * 8)) {
let mut buf = [0u8; PAGE_SIZE];
FreeMap::init_page(&mut buf);
assert!(!FreeMap::is_free(&buf, page_id));
FreeMap::mark_free(&mut buf, page_id);
assert!(FreeMap::is_free(&buf, page_id),
"page_id {} should be free after mark_free", page_id);
}
}
}